private MatchedFragmentIon MakeMatchedFragmentIon(IonType ionType, int ionIndex, Adduct adduct, TransitionLosses transitionLosses, out double matchMz) { var moleculeMasses = MoleculeMassesObj; int ordinal; int peptideLength = TargetInfoObj.LookupSequence.Sequence?.Length ?? 0; TypedMass matchMass, predictedMass; if (ionType == IonType.precursor) { matchMass = moleculeMasses.MatchIonMasses.PrecursorMass; predictedMass = moleculeMasses.PredictIonMasses.PrecursorMass; ordinal = peptideLength; } else { matchMass = moleculeMasses.MatchIonMasses.GetIonMass(ionType, ionIndex); predictedMass = moleculeMasses.PredictIonMasses.GetIonMass(ionType, ionIndex); ordinal = Transition.OffsetToOrdinal(ionType, ionIndex, peptideLength); } if (transitionLosses != null) { matchMass -= transitionLosses.Mass; predictedMass -= transitionLosses.Mass; } double predictedMz = SequenceMassCalc.GetMZ(predictedMass, adduct); matchMz = SequenceMassCalc.GetMZ(matchMass, adduct); return(new MatchedFragmentIon(ionType, ordinal, adduct, null, transitionLosses, predictedMz)); }
public TransitionDocNode(Transition id, Annotations annotations, TransitionLosses losses, double massH, TransitionIsotopeDistInfo isotopeDistInfo, TransitionLibInfo libInfo, Results <TransitionChromInfo> results) : base(id, annotations) { Losses = losses; if (losses != null) { massH -= losses.Mass; } if (id.IsCustom()) { Mz = new SignedMz(BioMassCalc.CalculateIonMz(massH, id.Charge), id.IsNegative()); } else { Mz = new SignedMz(SequenceMassCalc.GetMZ(massH, id.Charge) + SequenceMassCalc.GetPeptideInterval(id.DecoyMassShift), id.IsNegative()); } IsotopeDistInfo = isotopeDistInfo; LibInfo = libInfo; Results = results; }
private static CellDesc CreateIon(IonType type, int ordinal, TypedMass massH, Adduct charge, IEnumerable <DocNode> choices, ICollection <DocNode> chosen, Transition tranSelected, RenderTools rt) { double mz = SequenceMassCalc.GetMZ(massH, charge); CellDesc cell = CreateData(string.Format(@"{0:F02}", mz), rt); foreach (TransitionDocNode nodeTran in choices) { Transition tran = nodeTran.Transition; if (tran.IonType == type && tran.Ordinal == ordinal && tran.Adduct == charge) { cell.Font = rt.FontBold; if (Equals(tran, tranSelected)) { cell.Brush = rt.BrushSelected; // Stop after selected break; } if (!chosen.Contains(nodeTran)) { cell.Brush = rt.BrushChoice; // Keep looking } else { cell.Brush = rt.BrushChosen; // Stop after chosen break; } } } return(cell); }
public static TransitionLibInfo GetLibInfo(Transition transition, double massH, IDictionary <double, LibraryRankedSpectrumInfo.RankedMI> ranks) { LibraryRankedSpectrumInfo.RankedMI rmi; if (ranks == null || !ranks.TryGetValue(SequenceMassCalc.GetMZ(massH, transition.Charge), out rmi)) { return(null); } return(new TransitionLibInfo(rmi.Rank, rmi.Intensity)); }
/// <summary> /// Calculates the matching charge within a tolerance for a mass, assuming (de)protonation. /// </summary> /// <param name="mass">The mass to calculate charge for (actually massH if !IsCustomIon)</param> /// <param name="mz">The desired m/z value the charge should produce</param> /// <param name="tolerance">How far off the actual m/z is allowed to be</param> /// <param name="isCustomIon">Is this a custom ion formula?</param> /// <param name="minCharge">Minimum charge to consider</param> /// <param name="maxCharge">Maximum charge to consider</param> /// <param name="massShifts">Possible mass shifts that may have been applied to decoys</param> /// <param name="massShiftType"></param> /// <param name="massShift">Mass shift required to to achieve this charge state or zero</param> /// <param name="nearestCharge">closest matching charge, useful when return value is null</param> /// <returns>A matching charge or null, in which case the closest non-matching charge can be found in the nearestCharge value.</returns> public static Adduct CalcCharge(TypedMass mass, double mz, double tolerance, bool isCustomIon, int minCharge, int maxCharge, ICollection <int> massShifts, MassShiftType massShiftType, out int massShift, out int nearestCharge) { Assume.IsTrue(minCharge <= maxCharge); massShift = 0; nearestCharge = 0; double nearestDelta = double.MaxValue; for (int i = minCharge; i <= maxCharge; i++) { if (i != 0) // Avoid z=0 if we're entertaining negative charge states { double calculatedMz = isCustomIon ? Adduct.FromChargeProtonated(i).MzFromNeutralMass(mass) : SequenceMassCalc.GetMZ(mass, i); double delta = mz - calculatedMz; double deltaAbs = Math.Abs(delta); int potentialShift = (int)Math.Round(deltaAbs); double fractionalDelta = deltaAbs - potentialShift; if (MatchMz(fractionalDelta, tolerance) && MatchMassShift(potentialShift, massShifts, massShiftType)) { massShift = potentialShift; if (delta < 0) { massShift = -massShift; } var result = i; nearestCharge = i; return(Adduct.FromCharge(result, isCustomIon ? Adduct.ADDUCT_TYPE.non_proteomic : Adduct.ADDUCT_TYPE.proteomic)); } if (deltaAbs < nearestDelta) { nearestDelta = deltaAbs; nearestCharge = i; } // If the charge is positive and the calculated m/z is smaller than the desired m/z // increasing the charge further cannot possibly produce a match if (massShiftType == MassShiftType.none && minCharge > 0 && delta > 0) { break; } } } Debug.Assert(nearestCharge != 0); // Could only happen if min > max return(Adduct.EMPTY); }
public static TransitionQuantInfo GetLibTransitionQuantInfo(Transition transition, TransitionLosses losses, TypedMass massH, IDictionary <double, LibraryRankedSpectrumInfo.RankedMI> ranks) { LibraryRankedSpectrumInfo.RankedMI rmi = null; if (ranks != null) { ranks.TryGetValue(SequenceMassCalc.GetMZ(massH, transition.Adduct), out rmi); } TransitionLibInfo transitionLibInfo = null; if (rmi != null) { transitionLibInfo = new TransitionLibInfo(rmi.Rank, rmi.Intensity); } return(new TransitionQuantInfo(null, transitionLibInfo, rmi == null || rmi.Quantitative)); }
private List <SpectrumPeaksInfo.MI> CalcMIs(TypedMass mass, float[] intensities, int offset) { var result = new List <SpectrumPeaksInfo.MI>(PrositConstants.IONS_PER_RESIDUE / 2); for (var c = 0; c < PrositConstants.IONS_PER_RESIDUE / 2; ++c) { // Not a possible charge if (PeptidePrecursorNCE.NodeGroup.PrecursorCharge <= c) { break; } result.Add(new SpectrumPeaksInfo.MI { Mz = SequenceMassCalc.GetMZ(mass, c + 1), Intensity = intensities[c + offset] }); } return(result); }
public TransitionDocNode(Transition id, Annotations annotations, TransitionLosses losses, TypedMass mass, TransitionQuantInfo transitionQuantInfo, Results <TransitionChromInfo> results) : base(id, annotations) { Losses = losses; if (losses != null) { mass -= losses.Mass; } Mz = id.IsCustom() ? new SignedMz(id.Adduct.MzFromNeutralMass(mass), id.IsNegative()) : new SignedMz(SequenceMassCalc.GetMZ(mass, id.Adduct) + SequenceMassCalc.GetPeptideInterval(id.DecoyMassShift), id.IsNegative()); MzMassType = mass.MassType; IsotopeDistInfo = transitionQuantInfo.IsotopeDistInfo; LibInfo = transitionQuantInfo.LibInfo; Results = results; Quantitative = transitionQuantInfo.Quantititative; }
public IsotopeDistInfo(MassDistribution massDistribution, double monoisotopicMass, bool isMassH, // Is monoisotopicMass M+H, or just M as in small molecule use? int charge, Func <double, double> calcFilterWindow, double massResolution, double minimumAbundance) { _monoisotopicMass = monoisotopicMass; _charge = charge; _isMassH = isMassH; // Get peak center of mass values for the given resolution var q1FilterValues = MassDistribution.NewInstance(massDistribution, massResolution, 0).Keys.ToList(); // Find the monoisotopic m/z and make sure it is exactly the expected number double monoMz = isMassH ? SequenceMassCalc.GetMZ(_monoisotopicMass, _charge) : BioMassCalc.CalculateIonMz(_monoisotopicMass, _charge); double monoMzDist = monoMz; int monoMassIndex = 0; for (int i = 0; i < q1FilterValues.Count; i++) { double peakCenterMz = q1FilterValues[i]; double filterWindow = calcFilterWindow(peakCenterMz); double startMz = peakCenterMz - filterWindow / 2; double endMz = startMz + filterWindow; if (startMz < monoMz && monoMz < endMz) { monoMzDist = q1FilterValues[i]; q1FilterValues[i] = monoMz; monoMassIndex = i; break; } } // Insert a M-1 peak, even if it is not expected in the isotope mass distribution if (monoMassIndex == 0 && q1FilterValues.Count > 1) { // Use the delta from the original distribution monoMz to the next peak q1FilterValues.Insert(0, monoMz + monoMzDist - q1FilterValues[1]); monoMassIndex++; } if (!q1FilterValues.Any()) // As is small molecule docs with mz values only, no formulas { return; } // Use the filtering algorithm that will be used on real data to determine the // expected proportions of the mass distribution that will end up filtered into // peaks // CONSIDER: Mass accuracy information is not calculated here var key = new PrecursorTextId(q1FilterValues[monoMassIndex], null, ChromExtractor.summed); var filter = new SpectrumFilterPair(key, PeptideDocNode.UNKNOWN_COLOR, 0, null, null, null, null, 0, false, false); filter.AddQ1FilterValues(q1FilterValues, calcFilterWindow); var expectedSpectrum = filter.FilterQ1SpectrumList(new[] { new MsDataSpectrum { Mzs = massDistribution.Keys.ToArray(), Intensities = massDistribution.Values.ToArray() } }); int startIndex = expectedSpectrum.Intensities.IndexOf(inten => inten >= minimumAbundance); if (startIndex == -1) { throw new InvalidOperationException( string.Format(Resources.IsotopeDistInfo_IsotopeDistInfo_Minimum_abundance__0__too_high, minimumAbundance)); } // Always include the M-1 peak, even if it is expected to have zero intensity if (startIndex > monoMassIndex - 1) { startIndex = monoMassIndex - 1; } if (startIndex < 0) { startIndex = 0; } int endIndex = expectedSpectrum.Intensities.LastIndexOf(inten => inten >= minimumAbundance) + 1; int countPeaks = endIndex - startIndex; var listProportionIndices = new List <KeyValuePair <float, int> >(countPeaks); for (int i = 0; i < countPeaks; i++) { listProportionIndices.Add(new KeyValuePair <float, int>( expectedSpectrum.Intensities[i + startIndex], i)); } // Sort proportions descending. listProportionIndices.Sort((p1, p2) => Comparer.Default.Compare(p2.Key, p1.Key)); // Set proportions and ranks back in the original locations var expectedProportionRanks = new KeyValuePair <float, int> [countPeaks]; for (int i = 0; i < countPeaks; i++) { expectedProportionRanks[listProportionIndices[i].Value] = new KeyValuePair <float, int>(listProportionIndices[i].Key, i + 1); } // TODO: Can this be discarded? // MassDistribution = massDistribution; MonoMassIndex = monoMassIndex - startIndex; // Find the base peak and fill in the masses and proportions var expectedPeaks = new List <MzRankProportion>(); for (int i = 0; i < countPeaks; i++) { float expectedProportion = expectedProportionRanks[i].Key; int rank = expectedProportionRanks[i].Value; expectedPeaks.Add(new MzRankProportion(q1FilterValues[i + startIndex], rank, expectedProportion)); if (expectedProportion > expectedProportionRanks[BaseMassIndex].Key) { BaseMassIndex = i; } } ExpectedPeaks = expectedPeaks; }
private bool MatchNext(RankParams rp, IonType type, int offset, TransitionLosses losses, int charge, int len, bool filter, int end, int start, double startMz) { bool precursorMatch = Transition.IsPrecursor(type); double ionMass = !precursorMatch ? rp.massesMatch[(int)type, offset] : rp.massPreMatch; if (losses != null) { ionMass -= losses.Mass; } double ionMz = SequenceMassCalc.GetMZ(ionMass, charge); // Unless trying to match everything, stop looking outside the instrument range if (!rp.matchAll && !rp.HasLosses && ionMz > rp.maxMz) { return(false); } // Check filter properties, if apropriate if ((rp.matchAll || ionMz >= rp.minMz) && Math.Abs(ionMz - ObservedMz) < rp.tolerance) { // Make sure each m/z value is only used for the most intense peak // that is within the tolerance range. if (rp.IsSeen(ionMz)) { return(true); // Keep looking } rp.Seen(ionMz); int ordinal = Transition.OffsetToOrdinal(type, offset, len + 1); // If this m/z aready matched a different ion, just remember the second ion. double predictedMass = !precursorMatch ? rp.massesPredict[(int)type, offset] : rp.massPrePredict; if (losses != null) { predictedMass -= losses.Mass; } double predictedMz = SequenceMassCalc.GetMZ(predictedMass, charge); if (Ordinal > 0) { IonType2 = type; Charge2 = charge; Ordinal2 = ordinal; Losses2 = losses; PredictedMz2 = predictedMz; rp.matched = true; return(false); } // Avoid using the same predicted m/z on two different peaks if (predictedMz == ionMz || !rp.IsSeen(predictedMz)) { rp.Seen(predictedMz); // Avoid ranking precursor ions without losses, if the precursor isotopes will // not be taken from product ions if (!rp.excludePrecursorIsotopes || type != IonType.precursor || losses != null) { if (!filter || rp.tranSettings.Accept(rp.sequence, rp.precursorMz, type, offset, ionMz, start, end, startMz)) { if (!rp.matchAll || (rp.minMz <= ionMz && ionMz <= rp.maxMz && rp.rankTypes.Contains(type) && (rp.rankCharges.Contains(charge) || type == IonType.precursor))) { Rank = rp.RankNext(); } } } IonType = type; Charge = charge; Ordinal = ordinal; Losses = losses; PredictedMz = predictedMz; rp.matched = (!rp.matchAll); return(rp.matchAll); } } // Stop looking once the mass has been passed, unless there are losses to consider if (rp.HasLosses) { return(true); } return(ionMz <= ObservedMz); }
public IEnumerable <TransitionDocNode> GetPrecursorTransitions(SrmSettings settings, ExplicitMods mods, IPrecursorMassCalc calcPredictPre, IFragmentMassCalc calcPredict, double precursorMz, IsotopeDistInfo isotopeDist, IList <IList <ExplicitLoss> > potentialLosses, IDictionary <double, LibraryRankedSpectrumInfo.RankedMI> transitionRanks, bool libraryFilter, bool useFilter, bool ensureMassesAreMeasurable) { var tranSettings = settings.TransitionSettings; var fullScan = tranSettings.FullScan; int minMz = tranSettings.Instrument.GetMinMz(precursorMz); int maxMz = tranSettings.Instrument.MaxMz; bool precursorMS1 = fullScan.IsEnabledMs; MassType massType = tranSettings.Prediction.FragmentMassType; MassType massTypeIon = precursorMS1 ? tranSettings.Prediction.PrecursorMassType : massType; var sequence = Peptide.Target; var ionTypes = IsProteomic ? tranSettings.Filter.PeptideIonTypes : tranSettings.Filter.SmallMoleculeIonTypes; bool precursorNoProducts = precursorMS1 && !fullScan.IsEnabledMsMs && ionTypes.Count == 1 && ionTypes[0] == IonType.precursor; var precursorMassPredict = precursorMS1 ? calcPredictPre.GetPrecursorMass(sequence) : calcPredict.GetPrecursorFragmentMass(sequence); foreach (var losses in CalcTransitionLosses(IonType.precursor, 0, massType, potentialLosses)) { Adduct productAdduct; if (losses == null) { productAdduct = PrecursorAdduct; } else { productAdduct = losses.GetProductAdduct(PrecursorAdduct); if (productAdduct == null) { continue; } } double ionMz = IsProteomic ? SequenceMassCalc.GetMZ(Transition.CalcMass(precursorMassPredict, losses), PrecursorAdduct) : PrecursorAdduct.MzFromNeutralMass(CustomMolecule.GetMass(massTypeIon), massTypeIon); if (losses == null) { if (precursorMS1 && isotopeDist != null && ensureMassesAreMeasurable) { foreach (int i in fullScan.SelectMassIndices(isotopeDist, useFilter)) { var precursorMS1Mass = isotopeDist.GetMassI(i, DecoyMassShift); ionMz = SequenceMassCalc.GetMZ(precursorMS1Mass, PrecursorAdduct); if (minMz > ionMz || ionMz > maxMz) { continue; } var isotopeDistInfo = new TransitionIsotopeDistInfo( isotopeDist.GetRankI(i), isotopeDist.GetProportionI(i)); yield return(CreateTransitionNode(i, precursorMS1Mass, isotopeDistInfo, null, transitionRanks, productAdduct)); } continue; } } // If there was loss, it is possible (though not likely) that the ion m/z value // will now fall below the minimum measurable value for the instrument else if (ensureMassesAreMeasurable && minMz > ionMz) { continue; } // If filtering precursors from MS1 scans, then ranking in MS/MS does not apply bool precursorIsProduct = !precursorMS1 || losses != null; // Skip product ion precursors, if the should not be included if (useFilter && precursorIsProduct && precursorNoProducts) { continue; } if (!useFilter || !precursorIsProduct || !libraryFilter || IsMatched(transitionRanks, ionMz, IonType.precursor, PrecursorAdduct, losses)) { yield return(CreateTransitionNode(0, precursorMassPredict, null, losses, precursorIsProduct ? transitionRanks : null, productAdduct)); } } }
public IEnumerable <TransitionGroup> GetTransitionGroups(SrmSettings settings, PeptideDocNode nodePep, ExplicitMods mods, bool useFilter) { if (IsCustomMolecule) { // TODO(bspratt) WHY NOT USING TRANSITION SETTINGS FILTER PRECURSOR ADDUCTS? // We can't generate precursors as we do with peptides, so just filter what we do have on instrument mz range //var precursorAdducts = settings.TransitionSettings.Filter.SmallMoleculePrecursorAdducts; // TODO(bspratt) generate precursor transitions if doc has no fragments // CONSIDER(bspratt) could we reasonably reuse fragments with proposed precursors of suitable charge and polarity (say, add an M+Na node that mimics an existing M+H node and children) foreach (var group in nodePep.TransitionGroups.Where(tranGroup => tranGroup.TransitionGroup.IsCustomIon)) { if (!useFilter || settings.TransitionSettings.IsMeasurablePrecursor(group.PrecursorMz)) { yield return(group.TransitionGroup); } } } else { var precursorCharges = settings.TransitionSettings.Filter.PeptidePrecursorCharges; if (!useFilter) { precursorCharges = new List <Adduct>(); for (int i = TransitionGroup.MIN_PRECURSOR_CHARGE; i < TransitionGroup.MAX_PRECURSOR_CHARGE; i++) { precursorCharges.Add(Adduct.FromChargeProtonated(i)); } } var modSettings = settings.PeptideSettings.Modifications; var precursorMassLight = settings.GetPrecursorMass(IsotopeLabelType.light, Target, mods); var listPrecursorMasses = new List <KeyValuePair <IsotopeLabelType, TypedMass> > { new KeyValuePair <IsotopeLabelType, TypedMass>(IsotopeLabelType.light, precursorMassLight) }; foreach (var typeMods in modSettings.GetHeavyModifications()) { IsotopeLabelType labelType = typeMods.LabelType; var precursorMass = precursorMassLight; if (settings.HasPrecursorCalc(labelType, mods)) { precursorMass = settings.GetPrecursorMass(labelType, Target, mods); } listPrecursorMasses.Add(new KeyValuePair <IsotopeLabelType, TypedMass>(labelType, precursorMass)); } foreach (var adduct in precursorCharges) { if (useFilter && !settings.Accept(settings, this, mods, adduct)) { continue; } for (int i = 0; i < listPrecursorMasses.Count; i++) { var pair = listPrecursorMasses[i]; IsotopeLabelType labelType = pair.Key; var precursorMass = pair.Value; // Only return a heavy group, if the precursor masses differ // between the light and heavy calculators if (i == 0 || precursorMass != precursorMassLight) { if (settings.TransitionSettings.IsMeasurablePrecursor(SequenceMassCalc.GetMZ(precursorMass, adduct))) { yield return(new TransitionGroup(this, adduct, labelType)); } } } } } }
/// <summary> /// Calculates the matching charge within a tolerance for a mass. /// </summary> /// <param name="massH">The mass to calculate charge for</param> /// <param name="mz">The desired m/z value the charge should produce</param> /// <param name="tolerance">How far off the actual m/z is allowed to be</param> /// <param name="isCustomIon">Is this a custom ion formula?</param> /// <param name="minCharge">Minimum charge to consider</param> /// <param name="maxCharge">Maximum charge to consider</param> /// <param name="massShifts">Possible mass shifts that may have been applied to decoys</param> /// <param name="massShiftType"></param> /// <param name="massShift">Mass shift required to to achieve this charge state or zero</param> /// <param name="nearestCharge">closest matching charge, useful when return value is null</param> /// <returns>A matching charge or null, in which case the closest non-matching charge can be found in the nearestCharge value.</returns> public static int?CalcCharge(double massH, double mz, double tolerance, bool isCustomIon, int minCharge, int maxCharge, ICollection <int> massShifts, MassShiftType massShiftType, out int massShift, out int nearestCharge) { Assume.IsTrue(minCharge <= maxCharge); massShift = 0; nearestCharge = 0; double nearestDelta = double.MaxValue; for (int i = minCharge; i <= maxCharge; i++) { if (i != 0) // Avoid z=0 if we're entertaining negative charge states { double delta = mz - (isCustomIon ? BioMassCalc.CalculateIonMz(massH, i) : SequenceMassCalc.GetMZ(massH, i)); double deltaAbs = Math.Abs(delta); int potentialShift = (int)Math.Round(deltaAbs); double fractionalDelta = deltaAbs - potentialShift; if (MatchMz(fractionalDelta, tolerance) && MatchMassShift(potentialShift, massShifts, massShiftType)) { massShift = potentialShift; if (delta < 0) { massShift = -massShift; } int?result = i; nearestCharge = i; return(result); } if (deltaAbs < nearestDelta) { nearestDelta = deltaAbs; nearestCharge = i; } } } Debug.Assert(nearestCharge != 0); // Could only happen if min > max return(null); }
public static int CalcProductCharge(double productPrecursorMass, int precursorCharge, double[,] productMasses, IList <IList <ExplicitLoss> > potentialLosses, double productMz, double tolerance, MassType massType, MassShiftType massShiftType, out IonType?ionType, out int?ordinal, out TransitionLosses losses, out int massShift) { // Get length of fragment ion mass array int len = productMasses.GetLength(1); // Check all possible ion types and offsets double minDelta = double.MaxValue, minDeltaNs = double.MaxValue; int bestCharge = 0, bestChargeNs = 0; IonType? bestIonType = null, bestIonTypeNs = null; int? bestOrdinal = null, bestOrdinalNs = null; TransitionLosses bestLosses = null, bestLossesNs = null; int bestMassShift = 0; // Check to see if it is the precursor foreach (var lossesTrial in TransitionGroup.CalcTransitionLosses(IonType.precursor, 0, massType, potentialLosses)) { double productMass = productPrecursorMass - (lossesTrial != null ? lossesTrial.Mass : 0); int potentialMassShift; int nearestCharge; int? charge = CalcProductCharge(productMass, productMz, tolerance, false, precursorCharge, massShiftType, out potentialMassShift, out nearestCharge); if (charge.HasValue && charge.Value == precursorCharge) { double potentialMz = SequenceMassCalc.GetMZ(productMass, charge.Value) + potentialMassShift; double delta = Math.Abs(productMz - potentialMz); if (potentialMassShift == 0 && minDeltaNs > delta) { bestChargeNs = charge.Value; bestIonTypeNs = IonType.precursor; bestOrdinalNs = len + 1; bestLossesNs = lossesTrial; minDeltaNs = delta; } else if (potentialMassShift != 0 && minDelta > delta) { bestCharge = charge.Value; bestIonType = IonType.precursor; bestOrdinal = len + 1; bestLosses = lossesTrial; bestMassShift = potentialMassShift; minDelta = delta; } } } foreach (IonType type in Transition.ALL_TYPES) { // Types have priorities. If moving to a lower priority type, and there is already a // suitable answer stop looking. if ((type == Transition.ALL_TYPES[2] || type == Transition.ALL_TYPES[2]) && (MatchMz(minDelta, tolerance) || MatchMz(minDeltaNs, tolerance))) { break; } for (int offset = 0; offset < len; offset++) { foreach (var lossesTrial in TransitionGroup.CalcTransitionLosses(type, offset, massType, potentialLosses)) { // Look for the closest match. double productMass = productMasses[(int)type, offset]; if (lossesTrial != null) { productMass -= lossesTrial.Mass; } int potentialMassShift; int nearestCharge; int?chargeFound = CalcProductCharge(productMass, productMz, tolerance, false, precursorCharge, massShiftType, out potentialMassShift, out nearestCharge); if (chargeFound.HasValue) { int charge = chargeFound.Value; double potentialMz = SequenceMassCalc.GetMZ(productMass, charge) + potentialMassShift; double delta = Math.Abs(productMz - potentialMz); if (potentialMassShift == 0 && minDeltaNs > delta) { bestChargeNs = charge; bestIonTypeNs = type; // The peptide length is 1 longer than the mass array bestOrdinalNs = Transition.OffsetToOrdinal(type, offset, len + 1); bestLossesNs = lossesTrial; minDeltaNs = delta; } else if (potentialMassShift != 0 && minDelta > delta) { bestCharge = charge; bestIonType = type; // The peptide length is 1 longer than the mass array bestOrdinal = Transition.OffsetToOrdinal(type, offset, len + 1); bestLosses = lossesTrial; bestMassShift = potentialMassShift; minDelta = delta; } } } } } // Pefer no-shift to shift, even if the shift value is closer if (MatchMz(minDelta, tolerance) && !MatchMz(minDeltaNs, tolerance)) { ionType = bestIonType; ordinal = bestOrdinal; losses = bestLosses; massShift = bestMassShift; return(bestCharge); } ionType = bestIonTypeNs; ordinal = bestOrdinalNs; losses = bestLossesNs; massShift = 0; return(bestChargeNs); }
/// <summary> /// Test only method for creating a <see cref="BiblioSpecLibrary"/> file /// from another loaded <see cref="Library"/>. Should this move into test project? /// </summary> /// <param name="streamManager">Provides access to the file system</param> /// <param name="path">Path to write to</param> /// <param name="library">The loaded library to use as a data source</param> public static void Write(IStreamManager streamManager, string path, Library library) { using (FileSaver fs = new FileSaver(path, streamManager)) using (Stream outStream = streamManager.CreateStream(fs.SafeName, FileMode.Create, true)) { outStream.Write(BitConverter.GetBytes(library.SpectrumCount), 0, sizeof(int)); // num_spectra outStream.Write(BitConverter.GetBytes(0), 0, sizeof(int)); // filtered outStream.Write(BitConverter.GetBytes(1), 0, sizeof(int)); // version1 outStream.Write(BitConverter.GetBytes(1), 0, sizeof(int)); // version2 outStream.Write(BitConverter.GetBytes(0), 0, sizeof(int)); // next_id SequenceMassCalc calc = new SequenceMassCalc(MassType.Monoisotopic); byte[] seqBuffer = new byte[1024]; int scanNum = 1; foreach (var key in library.Keys) { SpectrumPeaksInfo peaksInfo; if (!library.TryLoadSpectrum(key, out peaksInfo)) { continue; } string sequence = key.Sequence; // Only works for unmodified sequence Debug.Assert(!key.IsModified); double precursorMH = calc.GetPrecursorMass(sequence); int charge = key.Charge; float precursorMz = (float)SequenceMassCalc.GetMZ(precursorMH, charge); outStream.Write(BitConverter.GetBytes(scanNum), 0, sizeof(int)); // scan_num outStream.Write(BitConverter.GetBytes(2), 0, sizeof(int)); // scan_type outStream.Write(BitConverter.GetBytes(precursorMz), 0, sizeof(float)); // pre_mz outStream.Write(BitConverter.GetBytes(charge), 0, sizeof(int)); // scan_type outStream.Write(BitConverter.GetBytes(0f), 0, sizeof(int)); // r_time outStream.Write(BitConverter.GetBytes(peaksInfo.Peaks.Length), 0, sizeof(int)); // num_peaks outStream.Write(BitConverter.GetBytes(0), 0, sizeof(int)); // 32-bit peak_ptr outStream.Write(BitConverter.GetBytes(sequence.Length), 0, sizeof(int)); // seq_len outStream.Write(BitConverter.GetBytes(0), 0, sizeof(int)); // annot outStream.Write(BitConverter.GetBytes(scanNum), 0, sizeof(int)); // copies (bogus value for ranking) outStream.Write(BitConverter.GetBytes(0), 0, sizeof(int)); // lib_id scanNum++; // Sequence int len = sequence.Length; seqBuffer[len] = 0; Encoding.UTF8.GetBytes(sequence, 0, len, seqBuffer, 0); outStream.Write(seqBuffer, 0, len + 1); // Modifications const string zeros = "000000000000000000000000000000000000000000000000000"; // Not L10N Encoding.UTF8.GetBytes(zeros.Substring(0, len), 0, len, seqBuffer, 0); outStream.Write(seqBuffer, 0, len + 1); // Peaks foreach (var mi in peaksInfo.Peaks) { outStream.Write(BitConverter.GetBytes((float)mi.Mz), 0, sizeof(float)); outStream.Write(BitConverter.GetBytes(mi.Intensity), 0, sizeof(float)); } } streamManager.Finish(outStream); fs.Commit(); } }
private LibraryRankedSpectrumInfo(SpectrumPeaksInfo info, IsotopeLabelType labelType, TransitionGroupDocNode groupDocNode, SrmSettings settings, Target lookupSequence, ExplicitMods lookupMods, IEnumerable <Adduct> charges, IEnumerable <IonType> types, IEnumerable <Adduct> rankCharges, IEnumerable <IonType> rankTypes, double?score, bool useFilter, bool matchAll, int minPeaks) { LabelType = labelType; // Avoid ReSharper multiple enumeration warning var rankChargesArray = rankCharges.ToArray(); var rankTypesArray = rankTypes.ToArray(); TransitionGroup group = groupDocNode.TransitionGroup; bool isProteomic = group.IsProteomic; if (score == null && groupDocNode.HasLibInfo && groupDocNode.LibInfo is BiblioSpecSpectrumHeaderInfo libInfo) { Score = libInfo.Score; } else { Score = score; } if (!useFilter) { if (charges == null) { charges = GetRanked(rankChargesArray, isProteomic ? Transition.DEFAULT_PEPTIDE_CHARGES : Transition.DEFAULT_MOLECULE_CHARGES); } if (types == null) { types = GetRanked(rankTypesArray, isProteomic ? Transition.PEPTIDE_ION_TYPES : Transition.MOLECULE_ION_TYPES); } matchAll = true; } bool limitRanks = groupDocNode.IsCustomIon && // For small molecules, cap the number of ranked ions displayed if we don't have any peak metadata groupDocNode.Transitions.Any(t => string.IsNullOrEmpty(t.FragmentIonName)); RankParams rp = new RankParams { sequence = lookupSequence, precursorAdduct = group.PrecursorAdduct, adducts = charges ?? rankCharges, types = types ?? rankTypes, matchAll = matchAll, rankCharges = rankChargesArray.Select(a => Math.Abs(a.AdductCharge)).ToArray(), rankTypes = rankTypesArray, // Precursor isotopes will not be included in MS/MS, if they will be filtered // from MS1 excludePrecursorIsotopes = settings.TransitionSettings.FullScan.IsEnabledMs, tranSettings = settings.TransitionSettings, rankLimit = limitRanks ? settings.TransitionSettings.Libraries.IonCount : (int?)null }; // Get necessary mass calculators and masses var calcMatchPre = settings.GetPrecursorCalc(labelType, lookupMods); var calcMatch = isProteomic ? settings.GetFragmentCalc(labelType, lookupMods) : settings.GetDefaultFragmentCalc(); var calcPredict = isProteomic ? settings.GetFragmentCalc(group.LabelType, lookupMods) : calcMatch; if (isProteomic && rp.sequence.IsProteomic) { rp.precursorMz = SequenceMassCalc.GetMZ(calcMatchPre.GetPrecursorMass(rp.sequence), rp.precursorAdduct); rp.massPreMatch = calcMatch.GetPrecursorFragmentMass(rp.sequence); rp.massesMatch = calcMatch.GetFragmentIonMasses(rp.sequence); rp.knownFragments = null; } else if (!isProteomic && !rp.sequence.IsProteomic) { string isotopicForumla; rp.precursorMz = SequenceMassCalc.GetMZ(calcMatchPre.GetPrecursorMass(rp.sequence.Molecule, null, rp.precursorAdduct, out isotopicForumla), rp.precursorAdduct); rp.massPreMatch = calcMatch.GetPrecursorFragmentMass(rp.sequence); // rp.massesMatch = calcMatch.GetFragmentIonMasses(rp.molecule); CONSIDER, for some molecule types someday? // For small molecules we can't predict fragmentation, so just use those we have // Older Resharper code inspection implementations insist on warning here // Resharper disable PossibleMultipleEnumeration var existing = groupDocNode.Transitions.Where(tran => tran.Transition.IsNonPrecursorNonReporterCustomIon()).Select(t => t.Transition.CustomIon.GetMass(MassType.Monoisotopic)).ToArray(); rp.massesMatch = new IonTable <TypedMass>(IonType.custom, existing.Length); for (var i = 0; i < existing.Length; i++) { rp.massesMatch[IonType.custom, i] = existing[i]; } // Resharper restore PossibleMultipleEnumeration rp.knownFragments = groupDocNode.Transitions.Where(tran => tran.Transition.IsNonPrecursorNonReporterCustomIon()).Select(t => new KnownFragment { Adduct = t.Transition.Adduct, Name = t.GetFragmentIonName(CultureInfo.CurrentCulture, settings.TransitionSettings.Libraries.IonMatchTolerance), Mz = t.Mz }).ToList(); } else { rp.precursorMz = 0.0; rp.massPreMatch = TypedMass.ZERO_MONO_MASSH; rp.massesMatch = IonTable <TypedMass> .EMPTY; rp.knownFragments = null; } rp.massPrePredict = rp.massPreMatch; rp.massesPredict = rp.massesMatch; if (!ReferenceEquals(calcPredict, calcMatch)) { rp.massPrePredict = calcPredict.GetPrecursorFragmentMass(rp.sequence); if (rp.sequence.IsProteomic) // CONSIDER - eventually we may be able to predict fragments for small molecules? { rp.massesPredict = calcPredict.GetFragmentIonMasses(rp.sequence); } } // Get values of interest from the settings. var tranSettings = settings.TransitionSettings; var predict = tranSettings.Prediction; var filter = tranSettings.Filter; var libraries = tranSettings.Libraries; var instrument = tranSettings.Instrument; // Get potential losses to all fragments in this peptide rp.massType = predict.FragmentMassType; rp.potentialLosses = TransitionGroup.CalcPotentialLosses(rp.sequence, settings.PeptideSettings.Modifications, lookupMods, rp.massType); // Create arrays because ReadOnlyCollection enumerators are too slow // In some cases these collections must be enumerated for every ion // allowed in the library specturm. rp.startFinder = filter.FragmentRangeFirst; rp.endFinder = filter.FragmentRangeLast; // Get library settings Tolerance = libraries.IonMatchTolerance; rp.tolerance = Tolerance; rp.pick = tranSettings.Libraries.Pick; int ionMatchCount = libraries.IonCount; // If no library filtering will happen, return all rankings for view in the UI if (!useFilter || rp.pick == TransitionLibraryPick.none) { if (rp.pick == TransitionLibraryPick.none) { rp.pick = TransitionLibraryPick.all; } ionMatchCount = -1; } // Get instrument settings rp.minMz = instrument.MinMz; rp.maxMz = instrument.MaxMz; // 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 (ionMatchCount == -1) { 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[ionMatchCount != -1 ? ionMatchCount : arrayRMI.Length]; foreach (RankedMI rmi in arrayRMI) { rmi.CalculateRank(rp); // If not filtering for only the highest ionMatchCount ranks if (ionMatchCount == -1) { // Put the ranked record back where it started in the // m/z ordering to avoid a second sort. arrayResult[rmi.IndexMz] = rmi; } // Otherwise, if this ion was ranked, add it to the result array else if (rmi.Rank > 0) { int countRanks = rmi.Rank; arrayResult[countRanks - 1] = rmi; // And stop when the array is full if (countRanks == ionMatchCount) { 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 (rp.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 (ionMatchCount != -1 && arrayRMI.Any(rmi => rmi.HasAnnotations)) { // Pass through anything with an annotation as being of probable interest arrayResult = arrayRMI.Where(rmi => rmi.HasAnnotations).ToArray(); ionMatchCount = -1; } } // If not enough ranked ions were found, fill the rest of the results array if (ionMatchCount != -1) { for (int i = rp.Ranked; i < ionMatchCount; 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 necesary. else if (sortMz) { Array.Sort(arrayResult, OrderMz); } _spectrum = MakeReadOnly(arrayResult); }
public static Adduct CalcProductCharge(TypedMass productPrecursorMass, int?productZ, Adduct precursorCharge, IList <IonType> acceptedIonTypes, IonTable <TypedMass> productMasses, IList <IList <ExplicitLoss> > potentialLosses, double productMz, double tolerance, MassType massType, MassShiftType massShiftType, out IonType?ionType, out int?ordinal, out TransitionLosses losses, out int massShift) { // Get length of fragment ion mass array int len = productMasses.GetLength(1); // Check all possible ion types and offsets double?minDelta = null; double?minFragmentMass = null, maxFragmentMass = null, maxLoss = null; if (massShiftType == MassShiftType.none) { if (!productZ.HasValue) { minFragmentMass = productMz - tolerance; } else { minFragmentMass = SequenceMassCalc.GetMH(productMz - tolerance, productZ.Value); maxFragmentMass = SequenceMassCalc.GetMH(productMz + tolerance, productZ.Value); } } var bestCharge = Adduct.EMPTY; IonType? bestIonType = null; int? bestOrdinal = null; TransitionLosses bestLosses = null; int bestMassShift = 0; // Check to see if it is the precursor foreach (var lossesTrial in TransitionGroup.CalcTransitionLosses(IonType.precursor, 0, massType, potentialLosses)) { var productMass = productPrecursorMass; if (lossesTrial != null) { productMass -= lossesTrial.Mass; maxLoss = Math.Max(maxLoss ?? 0, lossesTrial.Mass); } int potentialMassShift; int nearestCharge; var charge = CalcProductCharge(productMass, productZ, productMz, tolerance, false, precursorCharge, massShiftType, out potentialMassShift, out nearestCharge); if (Equals(charge, precursorCharge)) { double potentialMz = SequenceMassCalc.GetMZ(productMass, charge) + potentialMassShift; double delta = Math.Abs(productMz - potentialMz); if (CompareIonMatch(delta, lossesTrial, potentialMassShift, minDelta, bestLosses, bestMassShift) < 0) { bestCharge = charge; bestIonType = IonType.precursor; bestOrdinal = len + 1; bestLosses = lossesTrial; bestMassShift = potentialMassShift; minDelta = delta; } } } if (maxLoss.HasValue) { maxFragmentMass += maxLoss.Value; } var categoryLast = -1; foreach (var typeAccepted in GetIonTypes(acceptedIonTypes)) { var type = typeAccepted.IonType; var category = typeAccepted.IonCategory; // Types have priorities. If changing type category, and there is already a // suitable answer stop looking. if (category != categoryLast && minDelta.HasValue && MatchMz(minDelta.Value, tolerance)) { break; } categoryLast = category; // The peptide length is 1 longer than the mass array for (int ord = len; ord > 0; ord--) { int offset = Transition.OrdinalToOffset(type, ord, len + 1); var productMassBase = productMasses[type, offset]; // Until below the maximum fragment mass no possible matches if (maxFragmentMass.HasValue && productMassBase > maxFragmentMass.Value) { continue; } // Once below the minimum fragment mass no more possible matches, so stop if (minFragmentMass.HasValue && productMassBase < minFragmentMass.Value) { break; } foreach (var lossesTrial in TransitionGroup.CalcTransitionLosses(type, offset, massType, potentialLosses)) { // Look for the closest match. var productMass = productMassBase; if (lossesTrial != null) { productMass -= lossesTrial.Mass; } int potentialMassShift; int nearestCharge; var chargeFound = CalcProductCharge(productMass, productZ, productMz, tolerance, false, precursorCharge, massShiftType, out potentialMassShift, out nearestCharge); if (!chargeFound.IsEmpty) { var charge = chargeFound; double potentialMz = SequenceMassCalc.GetMZ(productMass, charge) + potentialMassShift; double delta = Math.Abs(productMz - potentialMz); if (CompareIonMatch(delta, lossesTrial, potentialMassShift, minDelta, bestLosses, bestMassShift) < 0) { bestCharge = charge; bestIonType = type; bestOrdinal = ord; bestLosses = lossesTrial; bestMassShift = potentialMassShift; minDelta = delta; } } } } } ionType = bestIonType; ordinal = bestOrdinal; losses = bestLosses; massShift = bestMassShift; return(bestCharge); }
public IEnumerable <TransitionDocNode> GetPrecursorTransitions(SrmSettings settings, ExplicitMods mods, IPrecursorMassCalc calcFilterPre, IFragmentMassCalc calcPredict, double precursorMz, IsotopeDistInfo isotopeDist, IList <IList <ExplicitLoss> > potentialLosses, IDictionary <double, LibraryRankedSpectrumInfo.RankedMI> transitionRanks, bool libraryFilter, bool useFilter) { var tranSettings = settings.TransitionSettings; var fullScan = tranSettings.FullScan; MassType massType = tranSettings.Prediction.FragmentMassType; int minMz = tranSettings.Instrument.GetMinMz(precursorMz); int maxMz = tranSettings.Instrument.MaxMz; bool precursorMS1 = fullScan.IsEnabledMs; if (IsCustomIon) { var ionMz = BioMassCalc.CalculateIonMz( CustomIon.GetMass(settings.TransitionSettings.Prediction.PrecursorMassType), PrecursorCharge); if (!useFilter || !libraryFilter || IsMatched(transitionRanks, ionMz, IonType.precursor, PrecursorCharge, null)) { if (precursorMS1 && isotopeDist != null) { foreach (int i in fullScan.SelectMassIndices(isotopeDist, useFilter)) { double precursorMS1Mass = isotopeDist.GetMassI(i); ionMz = BioMassCalc.CalculateIonMz(precursorMS1Mass, PrecursorCharge); if (minMz > ionMz || ionMz > maxMz) { continue; } var isotopeDistInfo = new TransitionIsotopeDistInfo(isotopeDist.GetRankI(i), isotopeDist.GetProportionI(i)); yield return(CreateTransitionNode(i, precursorMS1Mass, isotopeDistInfo, null, transitionRanks, CustomIon)); } } else { var transition = new Transition(this, PrecursorCharge, null, CustomIon, IonType.precursor); double massH = CustomIon.GetMass(settings.TransitionSettings.Prediction.PrecursorMassType); yield return(new TransitionDocNode(transition, null, massH, null, null)); } } yield break; } string sequence = Peptide.Sequence; bool precursorNoProducts = precursorMS1 && !fullScan.IsEnabledMsMs && tranSettings.Filter.IonTypes.Count == 1 && tranSettings.Filter.IonTypes[0] == IonType.precursor; double precursorMassPredict = calcPredict.GetPrecursorFragmentMass(sequence); foreach (var losses in CalcTransitionLosses(IonType.precursor, 0, massType, potentialLosses)) { double ionMz = SequenceMassCalc.GetMZ(Transition.CalcMass(precursorMassPredict, losses), PrecursorCharge); if (losses == null) { if (precursorMS1 && isotopeDist != null) { foreach (int i in fullScan.SelectMassIndices(isotopeDist, useFilter)) { double precursorMS1Mass = isotopeDist.GetMassI(i, DecoyMassShift); ionMz = SequenceMassCalc.GetMZ(precursorMS1Mass, PrecursorCharge); if (minMz > ionMz || ionMz > maxMz) { continue; } var isotopeDistInfo = new TransitionIsotopeDistInfo( isotopeDist.GetRankI(i), isotopeDist.GetProportionI(i)); yield return(CreateTransitionNode(i, precursorMS1Mass, isotopeDistInfo, null, transitionRanks)); } continue; } } // If there was loss, it is possible (though not likely) that the ion m/z value // will now fall below the minimum measurable value for the instrument else if (minMz > ionMz) { continue; } // If filtering precursors from MS1 scans, then ranking in MS/MS does not apply bool precursorIsProduct = !precursorMS1 || losses != null; // Skip product ion precursors, if the should not be included if (useFilter && precursorIsProduct && precursorNoProducts) { continue; } if (!useFilter || !precursorIsProduct || !libraryFilter || IsMatched(transitionRanks, ionMz, IonType.precursor, PrecursorCharge, losses)) { yield return(CreateTransitionNode(0, precursorMassPredict, null, losses, precursorIsProduct ? transitionRanks : null)); } } }
public IEnumerable <TransitionDocNode> GetTransitions(SrmSettings settings, TransitionGroupDocNode groupDocNode, ExplicitMods mods, double precursorMz, IsotopeDistInfo isotopeDist, SpectrumHeaderInfo libInfo, IDictionary <double, LibraryRankedSpectrumInfo.RankedMI> transitionRanks, bool useFilter) { Assume.IsTrue(ReferenceEquals(groupDocNode.TransitionGroup, this)); // Get necessary mass calculators and masses var calcFilterPre = settings.GetPrecursorCalc(IsotopeLabelType.light, mods); var calcFilter = settings.GetFragmentCalc(IsotopeLabelType.light, mods); var calcPredict = settings.GetFragmentCalc(LabelType, mods); string sequence = Peptide.Sequence; // Save the true precursor m/z for TranstionSettings.Accept() now that all isotope types are // checked. This is more correct than just using the light precursor m/z for precursor window // exclusion. double precursorMzAccept = precursorMz; if (!ReferenceEquals(calcFilter, calcPredict)) { // Get the normal precursor m/z for filtering, so that light and heavy ion picks will match. precursorMz = IsCustomIon ? BioMassCalc.CalculateIonMz(calcFilterPre.GetPrecursorMass(groupDocNode.CustomIon), groupDocNode.TransitionGroup.PrecursorCharge) : SequenceMassCalc.GetMZ(calcFilterPre.GetPrecursorMass(sequence), groupDocNode.TransitionGroup.PrecursorCharge); } if (!IsAvoidMismatchedIsotopeTransitions) { precursorMzAccept = precursorMz; } var tranSettings = settings.TransitionSettings; var filter = tranSettings.Filter; var charges = filter.ProductCharges; var startFinder = filter.FragmentRangeFirst; var endFinder = filter.FragmentRangeLast; double precursorMzWindow = filter.PrecursorMzWindow; var types = filter.IonTypes; MassType massType = tranSettings.Prediction.FragmentMassType; int minMz = tranSettings.Instrument.GetMinMz(precursorMzAccept); int maxMz = tranSettings.Instrument.MaxMz; var pepMods = settings.PeptideSettings.Modifications; var potentialLosses = CalcPotentialLosses(sequence, pepMods, mods, massType); // A start m/z will need to be calculated if the start fragment // finder uses m/z and their are losses to consider. If the filter // is set to only consider fragments with m/z greater than the // precursor, the code below needs to also prevent loss fragments // from being under that m/z. double startMz = 0; // Get library settings var pick = tranSettings.Libraries.Pick; if (!useFilter) { pick = TransitionLibraryPick.all; var listAll = Transition.ALL_CHARGES.ToList(); listAll.AddRange(charges.Where(c => !Transition.ALL_CHARGES.Contains(c))); listAll.Sort(); charges = listAll.ToArray(); types = Transition.ALL_TYPES; } // If there are no libraries or no library information, then // picking cannot use library information else if (!settings.PeptideSettings.Libraries.HasLibraries || libInfo == null) { pick = TransitionLibraryPick.none; } // If filtering without library picking if (potentialLosses != null) { if (pick == TransitionLibraryPick.none) { // Only include loss combinations where all losses are included always potentialLosses = potentialLosses.Where(losses => losses.All(loss => loss.TransitionLoss.Loss.Inclusion == LossInclusion.Always)).ToArray(); } else if (useFilter) { // Exclude all losses which should never be included by default potentialLosses = potentialLosses.Where(losses => losses.All(loss => loss.TransitionLoss.Loss.Inclusion != LossInclusion.Never)).ToArray(); } if (!potentialLosses.Any()) { potentialLosses = null; } } // Return precursor ions if (!useFilter || types.Contains(IonType.precursor)) { bool libraryFilter = (pick == TransitionLibraryPick.all || pick == TransitionLibraryPick.filter); foreach (var nodeTran in GetPrecursorTransitions(settings, mods, calcFilterPre, calcPredict, precursorMz, isotopeDist, potentialLosses, transitionRanks, libraryFilter, useFilter)) { if (minMz <= nodeTran.Mz && nodeTran.Mz <= maxMz) { yield return(nodeTran); } } } // Return special ions from settings, if this is a peptide if (!IsCustomIon) { // This is a peptide, but it may have custom transitions (reporter ions), check those foreach (var measuredIon in tranSettings.Filter.MeasuredIons.Where(m => m.IsCustom)) { if (useFilter && measuredIon.IsOptional) { continue; } var tran = new Transition(this, measuredIon.Charge, null, measuredIon.CustomIon); double mass = settings.GetFragmentMass(IsotopeLabelType.light, null, tran, null); var nodeTran = new TransitionDocNode(tran, null, mass, null, null); if (minMz <= nodeTran.Mz && nodeTran.Mz <= maxMz) { yield return(nodeTran); } } } // For small molecules we can't generate new nodes, so just mz filter those we have foreach (var nodeTran in groupDocNode.Transitions.Where(tran => tran.Transition.IsNonPrecursorNonReporterCustomIon())) { if (minMz <= nodeTran.Mz && nodeTran.Mz <= maxMz) { yield return(nodeTran); } } if (sequence == null) // Completely custom { yield break; } // If picking relies on library information if (useFilter && pick != TransitionLibraryPick.none) { // If it is not yet loaded, or nothing got ranked, return an empty enumeration if (!settings.PeptideSettings.Libraries.IsLoaded || (transitionRanks != null && transitionRanks.Count == 0)) { yield break; } } double[,] massesPredict = calcPredict.GetFragmentIonMasses(sequence); int len = massesPredict.GetLength(1); if (len == 0) { yield break; } double[,] massesFilter = massesPredict; if (!ReferenceEquals(calcFilter, calcPredict)) { // Get the normal m/z values for filtering, so that light and heavy // ion picks will match. massesFilter = calcFilter.GetFragmentIonMasses(sequence); } // Get types other than this to make sure matches are possible for all types var listOtherTypes = new List <Tuple <TransitionGroupDocNode, IFragmentMassCalc> >(); foreach (var labelType in settings.PeptideSettings.Modifications.GetModificationTypes()) { if (Equals(labelType, LabelType)) { continue; } var calc = settings.GetFragmentCalc(labelType, mods); if (calc == null) { continue; } var tranGroupOther = new TransitionGroup(Peptide, PrecursorCharge, labelType, false, DecoyMassShift); var nodeGroupOther = new TransitionGroupDocNode(tranGroupOther, Annotations.EMPTY, settings, mods, libInfo, ExplicitTransitionGroupValues.EMPTY, null, new TransitionDocNode[0], false); listOtherTypes.Add(new Tuple <TransitionGroupDocNode, IFragmentMassCalc>(nodeGroupOther, calc)); } // Loop over potential product ions picking transitions foreach (IonType type in types) { // Precursor type is handled above. if (type == IonType.precursor) { continue; } foreach (int charge in charges) { // Precursor charge can never be lower than product ion charge. if (Math.Abs(PrecursorCharge) < Math.Abs(charge)) { continue; } int start = 0, end = 0; if (pick != TransitionLibraryPick.all) { start = startFinder.FindStartFragment(massesFilter, type, charge, precursorMz, precursorMzWindow, out startMz); end = endFinder.FindEndFragment(type, start, len); if (Transition.IsCTerminal(type)) { Helpers.Swap(ref start, ref end); } } for (int i = 0; i < len; i++) { // Get the predicted m/z that would be used in the transition double massH = massesPredict[(int)type, i]; foreach (var losses in CalcTransitionLosses(type, i, massType, potentialLosses)) { double ionMz = SequenceMassCalc.GetMZ(Transition.CalcMass(massH, losses), charge); // Make sure the fragment m/z value falls within the valid instrument range. // CONSIDER: This means that a heavy transition might excede the instrument // range where a light one is accepted, leading to a disparity // between heavy and light transtions picked. if (minMz > ionMz || ionMz > maxMz) { continue; } TransitionDocNode nodeTranReturn = null; bool accept = true; if (pick == TransitionLibraryPick.all || pick == TransitionLibraryPick.all_plus) { if (!useFilter) { nodeTranReturn = CreateTransitionNode(type, i, charge, massH, losses, transitionRanks); accept = false; } else { if (IsMatched(transitionRanks, ionMz, type, charge, losses)) { nodeTranReturn = CreateTransitionNode(type, i, charge, massH, losses, transitionRanks); accept = false; } // If allowing library or filter, check the filter to decide whether to accept else if (pick == TransitionLibraryPick.all_plus && tranSettings.Accept(sequence, precursorMzAccept, type, i, ionMz, start, end, startMz)) { nodeTranReturn = CreateTransitionNode(type, i, charge, massH, losses, transitionRanks); } } } else if (tranSettings.Accept(sequence, precursorMzAccept, type, i, ionMz, start, end, startMz)) { if (pick == TransitionLibraryPick.none) { nodeTranReturn = CreateTransitionNode(type, i, charge, massH, losses, transitionRanks); } else { if (IsMatched(transitionRanks, ionMz, type, charge, losses)) { nodeTranReturn = CreateTransitionNode(type, i, charge, massH, losses, transitionRanks); } } } if (nodeTranReturn != null) { if (IsAvoidMismatchedIsotopeTransitions && !OtherLabelTypesAllowed(settings, minMz, maxMz, start, end, startMz, accept, groupDocNode, nodeTranReturn, listOtherTypes)) { continue; } Assume.IsTrue(minMz <= nodeTranReturn.Mz && nodeTranReturn.Mz <= maxMz); yield return(nodeTranReturn); } } } } } }
public void DoFullScanSettingsTest(RefinementSettings.ConvertToSmallMoleculesMode asSmallMolecules, out List <SrmDocument> docCheckPoints) { docCheckPoints = new List <SrmDocument>(); var doc0 = ResultsUtil.DeserializeDocument("MultiLabel.sky", GetType()); var refine = new RefinementSettings(); var docSM = refine.ConvertToSmallMolecules(doc0, ".", asSmallMolecules); docCheckPoints.Add(docSM); Assert.IsFalse(docSM.MoleculeTransitionGroups.Any(nodeGroup => nodeGroup.IsotopeDist != null)); AssertEx.Serializable(docSM, AssertEx.Cloned); double c13Delta = BioMassCalc.MONOISOTOPIC.GetMass(BioMassCalc.C13) - BioMassCalc.MONOISOTOPIC.GetMass(BioMassCalc.C); double n15Delta = BioMassCalc.MONOISOTOPIC.GetMass(BioMassCalc.N15) - BioMassCalc.MONOISOTOPIC.GetMass(BioMassCalc.N); // Verify isotope distributions calculated when MS1 filtering enabled var enrichments = IsotopeEnrichmentsList.DEFAULT; var docIsotopes = docSM.ChangeSettings(docSM.Settings.ChangeTransitionFullScan(fs => fs.ChangePrecursorIsotopes(FullScanPrecursorIsotopes.Count, 3, enrichments))); docCheckPoints.Add(docIsotopes); Assert.AreEqual(FullScanMassAnalyzerType.tof, docIsotopes.Settings.TransitionSettings.FullScan.PrecursorMassAnalyzer); Assert.IsFalse(docIsotopes.MoleculeTransitionGroups.Any(nodeGroup => nodeGroup.IsotopeDist == null)); foreach (var nodeGroup in docIsotopes.MoleculeTransitionGroups) { Assert.AreEqual(3, nodeGroup.Children.Count); var isotopePeaks = nodeGroup.IsotopeDist; Assert.IsNotNull(isotopePeaks); Assert.IsTrue(nodeGroup.HasIsotopeDist); // The peaks should always includ at least M-1 Assume.IsTrue(isotopePeaks.MassIndexToPeakIndex(0) > 0); // Within 2.5% of 100% of the entire isotope distribution Assert.AreEqual(1.0, isotopePeaks.ExpectedProportions.Sum(), 0.025); // Precursor mass and m/z values are expected to match exactly (well, within XML roundtrip accuracy anyway) Assert.AreEqual(nodeGroup.PrecursorMz, nodeGroup.IsotopeDist.GetMZI(0), SequenceMassCalc.MassTolerance); Assert.AreEqual(nodeGroup.PrecursorMz, nodeGroup.TransitionGroup.IsCustomIon ? BioMassCalc.CalculateIonMz(nodeGroup.IsotopeDist.GetMassI(0), nodeGroup.TransitionGroup.PrecursorAdduct.Unlabeled) : SequenceMassCalc.GetMZ(nodeGroup.IsotopeDist.GetMassI(0), nodeGroup.TransitionGroup.PrecursorAdduct), SequenceMassCalc.MassTolerance); // Check isotope distribution masses for (int i = 1; i < isotopePeaks.CountPeaks; i++) { int massIndex = isotopePeaks.PeakIndexToMassIndex(i); Assert.IsTrue(isotopePeaks.GetMZI(massIndex - 1) < isotopePeaks.GetMZI(massIndex)); double massDelta = GetMassDelta(isotopePeaks, massIndex); if (nodeGroup.TransitionGroup.LabelType.IsLight) { // All positive should be close to 13C - C, and 0 should be the same as the next delta double expectedDelta = (massIndex > 0 ? c13Delta : GetMassDelta(isotopePeaks, massIndex + 1)); Assert.AreEqual(expectedDelta, massDelta, 0.001); } else if (nodeGroup.TransitionGroup.LabelType.Name.Contains("15N")) { // All positive should be close to 13C, and all negative 15N double expectedDelta = (massIndex > 0 ? c13Delta : n15Delta); Assert.AreEqual(expectedDelta, massDelta, 0.0015); } else if (massIndex == 0) { double expectedDelta = (isotopePeaks.GetProportionI(massIndex - 1) == 0 ? GetMassDelta(isotopePeaks, massIndex + 1) : 1.0017); Assert.AreEqual(expectedDelta, massDelta, 0.001); } else { Assert.AreEqual(c13Delta, massDelta, 0.001); } } } AssertEx.Serializable(docIsotopes, AssertEx.Cloned); // Narrow the resolution, and verify that predicted proportion of the isotope // distribution captured is reduced for all precursors var docIsotopesFt = docIsotopes.ChangeSettings(docIsotopes.Settings.ChangeTransitionFullScan(fs => fs.ChangePrecursorResolution(FullScanMassAnalyzerType.ft_icr, 500 * 1000, 400))); docCheckPoints.Add(docIsotopesFt); var tranGroupsOld = docIsotopes.MoleculeTransitionGroups.ToArray(); var tranGroupsNew = docIsotopesFt.MoleculeTransitionGroups.ToArray(); Assume.AreEqual(tranGroupsOld.Length, tranGroupsNew.Length); for (int i = 0; i < tranGroupsOld.Length; i++) { Assert.AreNotSame(tranGroupsOld[i], tranGroupsNew[i]); Assert.AreNotSame(tranGroupsOld[i].IsotopeDist, tranGroupsNew[i].IsotopeDist); Assert.IsTrue(tranGroupsOld[i].IsotopeDist.ExpectedProportions.Sum() > tranGroupsNew[i].IsotopeDist.ExpectedProportions.Sum()); } // Use Min % of base peak and verify variation in transitions used const float minPercent1 = 10; var docIsotopesP1 = docIsotopes.ChangeSettings(docIsotopes.Settings.ChangeTransitionFullScan(fs => fs.ChangePrecursorIsotopes(FullScanPrecursorIsotopes.Percent, minPercent1, enrichments))); docCheckPoints.Add(docIsotopesP1); tranGroupsNew = docIsotopesP1.MoleculeTransitionGroups.ToArray(); int maxTran = 0; for (int i = 0; i < tranGroupsOld.Length; i++) { // Isotope distributions should not have changed var isotopePeaks = tranGroupsNew[i].IsotopeDist; Assert.AreSame(tranGroupsOld[i].IsotopeDist, isotopePeaks); // Expected transitions should be present maxTran = Math.Max(maxTran, tranGroupsNew[i].Children.Count); foreach (TransitionDocNode nodeTran in tranGroupsNew[i].Children) { int massIndex = nodeTran.Transition.MassIndex; Assume.IsTrue(minPercent1 <= isotopePeaks.GetProportionI(massIndex) * 100.0 / isotopePeaks.BaseMassPercent); } } Assume.AreEqual(5, maxTran); AssertEx.Serializable(docIsotopesP1, AssertEx.Cloned); // Express any failure in terms of XML diffs // Use 10%, and check that 15N modifications all have M-1 const float minPercent2 = 5; var docIsotopesP2 = docIsotopesP1.ChangeSettings(docIsotopesP1.Settings.ChangeTransitionFullScan(fs => fs.ChangePrecursorIsotopes(FullScanPrecursorIsotopes.Percent, minPercent2, enrichments))); docCheckPoints.Add(docIsotopesP2); foreach (var nodeGroup in docIsotopesP2.MoleculeTransitionGroups) { var firstChild = (TransitionDocNode)nodeGroup.Children[0]; if (nodeGroup.TransitionGroup.LabelType.Name.EndsWith("15N")) { Assume.AreEqual(-1, firstChild.Transition.MassIndex); } else { Assume.AreNotEqual(-1, firstChild.Transition.MassIndex); } } AssertEx.Serializable(docIsotopesP2, AssertEx.Cloned); // Use lower enrichment of 13C, and verify that this add M-1 for 13C labeled precursors var enrichmentsLow13C = enrichments.ChangeEnrichment(new IsotopeEnrichmentItem(BioMassCalc.C13, 0.9)); var docIsotopesLow13C = docIsotopesP1.ChangeSettings(docIsotopesP1.Settings.ChangeTransitionFullScan(fs => fs.ChangePrecursorIsotopes(FullScanPrecursorIsotopes.Percent, minPercent2, enrichmentsLow13C))); tranGroupsNew = docIsotopesLow13C.MoleculeTransitionGroups.ToArray(); for (int i = 0; i < tranGroupsOld.Length; i++) { var nodeGroup = tranGroupsNew[i]; if (!Equals(nodeGroup.TransitionGroup.LabelType.Name, "heavy")) { Assert.AreSame(tranGroupsOld[i].IsotopeDist, nodeGroup.IsotopeDist); } else { var firstChild = (TransitionDocNode)nodeGroup.Children[0]; Assert.IsTrue(firstChild.Transition.MassIndex < 0); } } AssertEx.Serializable(docIsotopesLow13C, AssertEx.Cloned); // Express any failure as XML diffs // Use 0%, and check that everything has M-1 and lower var enrichmentsLow = enrichmentsLow13C.ChangeEnrichment(new IsotopeEnrichmentItem(BioMassCalc.N15, 0.97)); var docIsotopesLowP0 = docIsotopesP1.ChangeSettings(docIsotopesP1.Settings.ChangeTransitionFullScan(fs => fs.ChangePrecursorIsotopes(FullScanPrecursorIsotopes.Percent, 0, enrichmentsLow))); docCheckPoints.Add(docIsotopesLowP0); foreach (var nodeGroup in docIsotopesLowP0.MoleculeTransitionGroups) { Assume.AreEqual(nodeGroup.IsotopeDist.CountPeaks, nodeGroup.Children.Count); var firstChild = (TransitionDocNode)nodeGroup.Children[0]; if (nodeGroup.TransitionGroup.LabelType.IsLight) { Assert.AreEqual(-1, firstChild.Transition.MassIndex); } else { Assert.IsTrue(-1 > firstChild.Transition.MassIndex); } } AssertEx.Serializable(docIsotopesLowP0, AssertEx.Cloned); // Test a document with variable and heavy modifications, which caused problems for // the original implementation var docVariable = ResultsUtil.DeserializeDocument("HeavyVariable.sky", GetType()); Assert.IsFalse(docVariable.MoleculeTransitionGroups.Any(nodeGroup => nodeGroup.IsotopeDist == null)); foreach (var nodeGroup in docVariable.MoleculeTransitionGroups) { var isotopePeaks = nodeGroup.IsotopeDist; Assert.IsNotNull(isotopePeaks); // The peaks should always includ at least M-1 Assert.IsTrue(isotopePeaks.MassIndexToPeakIndex(0) > 0); // Precursor mass and m/z values are expected to match exactly (well, within XML roundtrip tolerance anyway) var mzI = nodeGroup.IsotopeDist.GetMZI(0); Assert.AreEqual(nodeGroup.PrecursorMz, mzI, SequenceMassCalc.MassTolerance); // Check isotope distribution masses for (int i = 1; i < isotopePeaks.CountPeaks; i++) { int massIndex = isotopePeaks.PeakIndexToMassIndex(i); Assert.IsTrue(isotopePeaks.GetMZI(massIndex - 1) < isotopePeaks.GetMZI(massIndex)); double massDelta = GetMassDelta(isotopePeaks, massIndex); bool containsSulfur = nodeGroup.TransitionGroup.Peptide.IsCustomMolecule ? (nodeGroup.CustomMolecule.Formula.IndexOfAny("S".ToCharArray()) != -1) : (nodeGroup.TransitionGroup.Peptide.Sequence.IndexOfAny("CM".ToCharArray()) != -1); if (massIndex == 0) { double expectedDelta = (isotopePeaks.GetProportionI(massIndex - 1) == 0 ? GetMassDelta(isotopePeaks, massIndex + 1) : 1.0017); Assert.AreEqual(expectedDelta, massDelta, 0.001); } else if (!containsSulfur || massIndex == 1) { Assert.AreEqual(c13Delta, massDelta, 0.001); } else { Assert.AreEqual(1.00075, massDelta, 0.001); } } } docCheckPoints.Add(docVariable); }
private LibraryRankedSpectrumInfo(SpectrumPeaksInfo info, IsotopeLabelType labelType, TransitionGroup group, SrmSettings settings, string lookupSequence, ExplicitMods lookupMods, IEnumerable <int> charges, IEnumerable <IonType> types, IEnumerable <int> rankCharges, IEnumerable <IonType> rankTypes, bool useFilter, bool matchAll, int minPeaks) { LabelType = labelType; // Avoid ReSharper multiple enumeration warning var rankChargesArray = rankCharges.ToArray(); var rankTypesArray = rankTypes.ToArray(); if (!useFilter) { if (charges == null) { charges = GetRanked(rankChargesArray, Transition.ALL_CHARGES); } if (types == null) { types = GetRanked(rankTypesArray, Transition.ALL_TYPES); } matchAll = true; } RankParams rp = new RankParams { sequence = lookupSequence, precursorCharge = group.PrecursorCharge, charges = charges ?? rankCharges, types = types ?? rankTypes, matchAll = matchAll, rankCharges = rankChargesArray, rankTypes = rankTypesArray, // Precursor isotopes will not be included in MS/MS, if they will be filtered // from MS1 excludePrecursorIsotopes = settings.TransitionSettings.FullScan.IsEnabledMs, tranSettings = settings.TransitionSettings }; // Get necessary mass calculators and masses var calcMatchPre = settings.GetPrecursorCalc(labelType, lookupMods); var calcMatch = settings.GetFragmentCalc(labelType, lookupMods); var calcPredict = settings.GetFragmentCalc(group.LabelType, lookupMods); if (!string.IsNullOrEmpty(rp.sequence)) { rp.precursorMz = SequenceMassCalc.GetMZ(calcMatchPre.GetPrecursorMass(rp.sequence), rp.precursorCharge); rp.massPreMatch = calcMatch.GetPrecursorFragmentMass(rp.sequence); rp.massesMatch = calcMatch.GetFragmentIonMasses(rp.sequence); } else { rp.precursorMz = 0.0; rp.massPreMatch = 0.0; rp.massesMatch = new double[0, 0]; } rp.massPrePredict = rp.massPreMatch; rp.massesPredict = rp.massesMatch; if (!ReferenceEquals(calcPredict, calcMatch) && !string.IsNullOrEmpty(rp.sequence)) { rp.massPrePredict = calcPredict.GetPrecursorFragmentMass(rp.sequence); rp.massesPredict = calcPredict.GetFragmentIonMasses(rp.sequence); } // Get values of interest from the settings. var tranSettings = settings.TransitionSettings; var predict = tranSettings.Prediction; var filter = tranSettings.Filter; var libraries = tranSettings.Libraries; var instrument = tranSettings.Instrument; // Get potential losses to all fragments in this peptide rp.massType = predict.FragmentMassType; rp.potentialLosses = TransitionGroup.CalcPotentialLosses(rp.sequence, settings.PeptideSettings.Modifications, lookupMods, rp.massType); // Create arrays because ReadOnlyCollection enumerators are too slow // In some cases these collections must be enumerated for every ion // allowed in the library specturm. rp.startFinder = filter.FragmentRangeFirst; rp.endFinder = filter.FragmentRangeLast; // Get library settings Tolerance = libraries.IonMatchTolerance; rp.tolerance = Tolerance; rp.pick = tranSettings.Libraries.Pick; int ionMatchCount = libraries.IonCount; // If no library filtering will happen, return all rankings for view in the UI if (!useFilter || rp.pick == TransitionLibraryPick.none) { if (rp.pick == TransitionLibraryPick.none) { rp.pick = TransitionLibraryPick.all; } ionMatchCount = -1; } // Get instrument settings rp.minMz = instrument.MinMz; rp.maxMz = instrument.MaxMz; // 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 (ionMatchCount == -1) { if (mi.Mz < lastMz) { sortMz = true; } lastMz = mi.Mz; } } // The one expensive sort is used to determine rank order // by intensity. Array.Sort(arrayRMI, OrderIntensityDesc); RankedMI[] arrayResult = new RankedMI[ionMatchCount != -1 ? ionMatchCount : arrayRMI.Length]; foreach (RankedMI rmi in arrayRMI) { rmi.CalculateRank(rp); // If not filtering for only the highest ionMatchCount ranks if (ionMatchCount == -1) { // Put the ranked record back where it started in the // m/z ordering to avoid a second sort. arrayResult[rmi.IndexMz] = rmi; } // Otherwise, if this ion was ranked, add it to the result array else if (rmi.Rank > 0) { int countRanks = rmi.Rank; arrayResult[countRanks - 1] = rmi; // And stop when the array is full if (countRanks == ionMatchCount) { break; } } } // If not enough ranked ions were found, fill the rest of the results array if (ionMatchCount != -1) { for (int i = rp.Ranked; i < ionMatchCount; 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 necesary. else if (sortMz) { Array.Sort(arrayResult, OrderMz); } _spectrum = MakeReadOnly(arrayResult); }
public static Adduct CalcProductCharge(TypedMass productPrecursorMass, Adduct precursorCharge, IList <IonType> acceptedIonTypes, IonTable <TypedMass> productMasses, IList <IList <ExplicitLoss> > potentialLosses, double productMz, double tolerance, MassType massType, MassShiftType massShiftType, out IonType?ionType, out int?ordinal, out TransitionLosses losses, out int massShift) { // Get length of fragment ion mass array int len = productMasses.GetLength(1); // Check all possible ion types and offsets double? minDelta = null; var bestCharge = Adduct.EMPTY; IonType? bestIonType = null; int? bestOrdinal = null; TransitionLosses bestLosses = null; int bestMassShift = 0; // Check to see if it is the precursor foreach (var lossesTrial in TransitionGroup.CalcTransitionLosses(IonType.precursor, 0, massType, potentialLosses)) { var productMass = productPrecursorMass - (lossesTrial != null ? lossesTrial.Mass : 0); int potentialMassShift; int nearestCharge; var charge = CalcProductCharge(productMass, productMz, tolerance, false, precursorCharge, massShiftType, out potentialMassShift, out nearestCharge); if (Equals(charge, precursorCharge)) { double potentialMz = SequenceMassCalc.GetMZ(productMass, charge) + potentialMassShift; double delta = Math.Abs(productMz - potentialMz); if (CompareIonMatch(delta, lossesTrial, potentialMassShift, minDelta, bestLosses, bestMassShift) < 0) { bestCharge = charge; bestIonType = IonType.precursor; bestOrdinal = len + 1; bestLosses = lossesTrial; bestMassShift = potentialMassShift; minDelta = delta; } } } var categoryLast = -1; foreach (var typeAccepted in GetIonTypes(acceptedIonTypes)) { var type = typeAccepted.IonType; var category = typeAccepted.IonCategory; // Types have priorities. If changing type category, and there is already a // suitable answer stop looking. if (category != categoryLast && minDelta.HasValue && MatchMz(minDelta.Value, tolerance)) { break; } categoryLast = category; for (int offset = 0; offset < len; offset++) { foreach (var lossesTrial in TransitionGroup.CalcTransitionLosses(type, offset, massType, potentialLosses)) { // Look for the closest match. var productMass = productMasses[type, offset]; if (lossesTrial != null) { productMass -= lossesTrial.Mass; } int potentialMassShift; int nearestCharge; var chargeFound = CalcProductCharge(productMass, productMz, tolerance, false, precursorCharge, massShiftType, out potentialMassShift, out nearestCharge); if (!chargeFound.IsEmpty) { var charge = chargeFound; double potentialMz = SequenceMassCalc.GetMZ(productMass, charge) + potentialMassShift; double delta = Math.Abs(productMz - potentialMz); if (CompareIonMatch(delta, lossesTrial, potentialMassShift, minDelta, bestLosses, bestMassShift) < 0) { bestCharge = charge; bestIonType = type; // The peptide length is 1 longer than the mass array bestOrdinal = Transition.OffsetToOrdinal(type, offset, len + 1); bestLosses = lossesTrial; bestMassShift = potentialMassShift; minDelta = delta; } } } } } ionType = bestIonType; ordinal = bestOrdinal; losses = bestLosses; massShift = bestMassShift; return(bestCharge); }
private bool MatchNext(RankParams rp, IonType type, int offset, TransitionLosses losses, Adduct adduct, string fragmentName, int len, bool filter, int end, int start, double startMz) { bool isFragment = !Transition.IsPrecursor(type); var ionMass = isFragment ? rp.massesMatch[type, offset] : rp.massPreMatch; if (losses != null) { ionMass -= losses.Mass; } double ionMz = SequenceMassCalc.GetMZ(ionMass, adduct); // Unless trying to match everything, stop looking outside the instrument range if (!rp.matchAll && !rp.HasLosses && ionMz > rp.maxMz) { return(false); } // Check filter properties, if apropriate if ((rp.matchAll || ionMz >= rp.minMz) && Math.Abs(ionMz - ObservedMz) < rp.tolerance) { // Make sure each m/z value is only used for the most intense peak // that is within the tolerance range. if (rp.IsSeen(ionMz)) { return(true); // Keep looking } rp.Seen(ionMz); int ordinal = Transition.OffsetToOrdinal(type, offset, len + 1); // If this m/z aready matched a different ion, just remember the second ion. var predictedMass = isFragment ? rp.massesPredict[type, offset] : rp.massPrePredict; if (losses != null) { predictedMass -= losses.Mass; } double predictedMz = SequenceMassCalc.GetMZ(predictedMass, adduct); if (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. var match = new MatchedFragmentIon(type, ordinal, adduct, fragmentName, losses, predictedMz); if (Rank == 0 && ApplyRanking(rp, type, offset, losses, adduct, filter, start, end, startMz, ionMz)) { MatchedIons.Insert(0, match); } else { MatchedIons.Add(match); } if (MatchedIons.Count < RankParams.MAX_MATCH) { return(true); } rp.matched = true; return(false); } // Avoid using the same predicted m/z on two different peaks if (predictedMz == ionMz || !rp.IsSeen(predictedMz)) { rp.Seen(predictedMz); ApplyRanking(rp, type, offset, losses, adduct, filter, start, end, startMz, ionMz); MatchedIons = new List <MatchedFragmentIon> { new MatchedFragmentIon(type, ordinal, adduct, fragmentName, losses, predictedMz) }; rp.matched = !rp.matchAll; return(rp.matchAll); } } // Stop looking once the mass has been passed, unless there are losses to consider if (rp.HasLosses) { return(true); } return(ionMz <= ObservedMz); }
private bool MatchNext(RankParams rp, IonType type, int offset, TransitionLosses losses, int charge, int len, bool filter, int end, int start, double startMz) { bool isFragment = Transition.IsFragment(type); double ionMass = isFragment ? rp.massesMatch[(int)type, offset] : rp.massPreMatch; if (losses != null) { ionMass -= losses.Mass; } double ionMz = SequenceMassCalc.GetMZ(ionMass, charge); // Unless trying to match everything, stop looking outside the instrument range if (!rp.matchAll && !rp.HasLosses && ionMz > rp.maxMz) { return(false); } // Check filter properties, if apropriate if ((rp.matchAll || ionMz >= rp.minMz) && Math.Abs(ionMz - ObservedMz) < rp.tolerance) { // Make sure each m/z value is only used for the most intense peak // that is within the tolerance range. if (rp.IsSeen(ionMz)) { return(true); // Keep looking } rp.Seen(ionMz); int ordinal = Transition.OffsetToOrdinal(type, offset, len + 1); // If this m/z aready matched a different ion, just remember the second ion. double predictedMass = isFragment ? rp.massesPredict[(int)type, offset] : rp.massPrePredict; if (losses != null) { predictedMass -= losses.Mass; } double predictedMz = SequenceMassCalc.GetMZ(predictedMass, charge); if (Ordinal > 0) { // 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 (Rank == 0 && ApplyRanking(rp, type, offset, losses, charge, filter, start, end, startMz, ionMz)) { IonType2 = IonType; Charge2 = Charge; Ordinal2 = Ordinal; Losses2 = Losses; PredictedMz2 = PredictedMz; IonType = type; Charge = charge; Ordinal = ordinal; Losses = losses; PredictedMz = predictedMz; } else { IonType2 = type; Charge2 = charge; Ordinal2 = ordinal; Losses2 = losses; PredictedMz2 = predictedMz; } rp.matched = true; return(false); } // Avoid using the same predicted m/z on two different peaks if (predictedMz == ionMz || !rp.IsSeen(predictedMz)) { rp.Seen(predictedMz); ApplyRanking(rp, type, offset, losses, charge, filter, start, end, startMz, ionMz); IonType = type; Charge = charge; Ordinal = ordinal; Losses = losses; PredictedMz = predictedMz; rp.matched = (!rp.matchAll); return(rp.matchAll); } } // Stop looking once the mass has been passed, unless there are losses to consider if (rp.HasLosses) { return(true); } return(ionMz <= ObservedMz); }
public IEnumerable <TransitionGroup> GetTransitionGroups(SrmSettings settings, PeptideDocNode nodePep, ExplicitMods mods, bool useFilter) { if (IsCustomIon) { // We can't generate nodes as we do with peptides, so just filter what we do have on instrument mz range foreach (var group in nodePep.TransitionGroups.Where(tranGroup => tranGroup.TransitionGroup.IsCustomIon)) { if (!useFilter || settings.TransitionSettings.IsMeasurablePrecursor(group.PrecursorMz)) { yield return(group.TransitionGroup); } } } else { IList <int> precursorCharges = settings.TransitionSettings.Filter.PrecursorCharges; if (!useFilter) { precursorCharges = new List <int>(); for (int i = TransitionGroup.MIN_PRECURSOR_CHARGE; i < TransitionGroup.MAX_PRECURSOR_CHARGE; i++) { precursorCharges.Add(i); } } var modSettings = settings.PeptideSettings.Modifications; double precursorMassLight = settings.GetPrecursorMass(IsotopeLabelType.light, Sequence, mods); var listPrecursorMasses = new List <KeyValuePair <IsotopeLabelType, double> > { new KeyValuePair <IsotopeLabelType, double>(IsotopeLabelType.light, precursorMassLight) }; foreach (var typeMods in modSettings.GetHeavyModifications()) { IsotopeLabelType labelType = typeMods.LabelType; double precursorMass = precursorMassLight; if (settings.HasPrecursorCalc(labelType, mods)) { precursorMass = settings.GetPrecursorMass(labelType, Sequence, mods); } listPrecursorMasses.Add(new KeyValuePair <IsotopeLabelType, double>(labelType, precursorMass)); } foreach (int charge in precursorCharges) { if (useFilter && !settings.Accept(settings, this, mods, charge)) { continue; } for (int i = 0; i < listPrecursorMasses.Count; i++) { var pair = listPrecursorMasses[i]; IsotopeLabelType labelType = pair.Key; double precursorMass = pair.Value; // Only return a heavy group, if the precursor masses differ // between the light and heavy calculators if (i == 0 || precursorMass != precursorMassLight) { if (settings.TransitionSettings.IsMeasurablePrecursor(SequenceMassCalc.GetMZ(precursorMass, charge))) { yield return(new TransitionGroup(this, null, charge, labelType)); } } } } } }
public SpectrumRanker(TargetInfo targetInfo, SrmSettings settings, FragmentFilter fragmentFilter) { TargetInfoObj = targetInfo; FragmentFilterObj = fragmentFilter; var groupDocNode = TargetInfoObj.TransitionGroupDocNode; TransitionGroup group = groupDocNode.TransitionGroup; bool isProteomic = group.IsProteomic; bool limitRanks = groupDocNode.IsCustomIon && // For small molecules, cap the number of ranked ions displayed if we don't have any peak metadata groupDocNode.Transitions.Any(t => string.IsNullOrEmpty(t.FragmentIonName)); RankLimit = limitRanks ? settings.TransitionSettings.Libraries.IonCount : (int?)null; // Get necessary mass calculators and masses var labelType = targetInfo.SpectrumLabelType; var lookupMods = targetInfo.LookupMods; var calcMatchPre = settings.GetPrecursorCalc(labelType, lookupMods); var calcMatch = isProteomic ? settings.GetFragmentCalc(labelType, lookupMods) : settings.GetDefaultFragmentCalc(); var calcPredict = isProteomic ? settings.GetFragmentCalc(group.LabelType, lookupMods) : calcMatch; MoleculeMasses moleculeMasses; if (null != lookupMods && lookupMods.HasCrosslinks) { moleculeMasses = GetCrosslinkMasses(settings); } else { if (isProteomic && Sequence.IsProteomic) { moleculeMasses = new MoleculeMasses( SequenceMassCalc.GetMZ(calcMatchPre.GetPrecursorMass(Sequence), PrecursorAdduct), new IonMasses(calcMatch.GetPrecursorFragmentMass(Sequence), calcMatch.GetFragmentIonMasses(Sequence))); } else if (!isProteomic && !Sequence.IsProteomic) { string isotopicFormula; var knownFragments = new List <MatchedFragmentIon>(); foreach (var tran in groupDocNode.Transitions) { if (tran.Transition.IsNonPrecursorNonReporterCustomIon()) { knownFragments.Add(new MatchedFragmentIon(IonType.custom, knownFragments.Count + 1, tran.Transition.Adduct, tran.GetFragmentIonName(CultureInfo.CurrentCulture, settings.TransitionSettings.Libraries.IonMatchTolerance), null, tran.Mz)); } } var ionMasses = new IonMasses(calcMatch.GetPrecursorFragmentMass(Sequence), IonTable <TypedMass> .EMPTY) .ChangeKnownFragments(knownFragments); moleculeMasses = new MoleculeMasses( SequenceMassCalc.GetMZ( calcMatchPre.GetPrecursorMass(Sequence.Molecule, null, PrecursorAdduct, out isotopicFormula), PrecursorAdduct), ionMasses); } else { moleculeMasses = new MoleculeMasses(0.0, new IonMasses(TypedMass.ZERO_MONO_MASSH, IonTable <TypedMass> .EMPTY)); } if (!ReferenceEquals(calcPredict, calcMatch)) { var ionTable = moleculeMasses.MatchIonMasses.FragmentMasses; if (Sequence.IsProteomic ) // CONSIDER - eventually we may be able to predict fragments for small molecules? { ionTable = calcPredict.GetFragmentIonMasses(Sequence); } moleculeMasses = moleculeMasses.ChangePredictIonMasses(new IonMasses( calcPredict.GetPrecursorFragmentMass(Sequence), ionTable)); } } MoleculeMassesObj = moleculeMasses; // Get values of interest from the settings. TransitionSettings = settings.TransitionSettings; // Get potential losses to all fragments in this peptide PotentialLosses = TransitionGroup.CalcPotentialLosses(Sequence, settings.PeptideSettings.Modifications, lookupMods, MassType); }