コード例 #1
0
 private static int?CalcProductCharge(double productMassH, double productMz, double tolerance, bool isCustomIon,
                                      int maxCharge, MassShiftType massShiftType, out int massShift, out int nearestCharge)
 {
     return(CalcCharge(productMassH, productMz, tolerance, isCustomIon,
                       Transition.MIN_PRODUCT_CHARGE,
                       Math.Min(maxCharge, Transition.MAX_PRODUCT_CHARGE),
                       Transition.MassShifts,
                       massShiftType,
                       out massShift,
                       out nearestCharge));
 }
コード例 #2
0
ファイル: TransitionCalc.cs プロジェクト: laeubisoft/pwiz
        /// <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);
        }
コード例 #3
0
        /// <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);
        }
コード例 #4
0
ファイル: TransitionCalc.cs プロジェクト: lgatto/proteowizard
        /// <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;
        }
コード例 #5
0
ファイル: TransitionCalc.cs プロジェクト: lgatto/proteowizard
 private static bool MatchMassShift(int potentialShift, ICollection<int> massShifts, MassShiftType massShiftType)
 {
     return (massShiftType != MassShiftType.shift_only && potentialShift == 0) ||
            (massShiftType != MassShiftType.none && massShifts.Contains(potentialShift));
 }
コード例 #6
0
ファイル: TransitionCalc.cs プロジェクト: lgatto/proteowizard
 private static int? CalcProductCharge(double productMassH, double productMz, double tolerance, bool isCustomIon,
     int maxCharge, MassShiftType massShiftType, out int massShift, out int nearestCharge)
 {
     return CalcCharge(productMassH, productMz, tolerance, isCustomIon,
         Transition.MIN_PRODUCT_CHARGE,
         Math.Min(maxCharge, Transition.MAX_PRODUCT_CHARGE),
         Transition.MassShifts,
         massShiftType,
         out massShift,
         out nearestCharge);
 }
コード例 #7
0
ファイル: TransitionCalc.cs プロジェクト: lgatto/proteowizard
        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;
        }
コード例 #8
0
 private static bool MatchMassShift(int potentialShift, ICollection <int> massShifts, MassShiftType massShiftType)
 {
     return((massShiftType != MassShiftType.shift_only && potentialShift == 0) ||
            (massShiftType != MassShiftType.none && massShifts.Contains(potentialShift)));
 }
コード例 #9
0
        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);
        }
コード例 #10
0
ファイル: TransitionCalc.cs プロジェクト: laeubisoft/pwiz
        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);
        }
コード例 #11
0
ファイル: TransitionCalc.cs プロジェクト: rfellers/pwiz
        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);
        }