Ejemplo n.º 1
0
        public override string ToString()
        {
            if (IsPrecursor())
            {
                return(Resources.Transition_ToString_precursor + GetChargeIndicator(Charge) +
                       GetMassIndexText(MassIndex));
            }

            if (IsCustom())
            {
                var text = CustomIon.ToString();
                // Was there enough information to generate a string more distinctive that just "Ion"?
                if (String.IsNullOrEmpty(CustomIon.Name) &&
                    String.IsNullOrEmpty(CustomIon.Formula))
                {
                    // No, add mz and charge to whatever generic text was used to describe it
                    var mz = BioMassCalc.CalculateIonMz(CustomIon.MonoisotopicMass, Charge);
                    return(string.Format("{0} {1:F04}{2}",  // Not L10N
                                         text, mz, GetChargeIndicator(Charge)));
                }
                return(text);
            }
            return(string.Format("{0} - {1}{2}{3}{4}", // Not L10N
                                 AA,
                                 IonType.ToString().ToLowerInvariant(),
                                 Ordinal,
                                 GetDecoyText(DecoyMassShift),
                                 GetChargeIndicator(Charge)));
        }
Ejemplo n.º 2
0
 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;
 }
Ejemplo n.º 3
0
        private static void TestEditingTransition()
        {
            double massPrecisionTolerance = Math.Pow(10, -SequenceMassCalc.MassPrecision);
            var    doc = SkylineWindow.Document;

            RunUI(() =>
            {
                SkylineWindow.ExpandPrecursors();
                SkylineWindow.SequenceTree.SelectedNode = SkylineWindow.SequenceTree.Nodes[0].FirstNode.FirstNode.FirstNode;
            });
            var editMoleculeDlg =
                ShowDialog <EditCustomMoleculeDlg>(
                    () => SkylineWindow.ModifyTransition((TransitionTreeNode)SkylineWindow.SequenceTree.SelectedNode));

            RunUI(() =>
            {
                Assert.AreEqual(C12H12, editMoleculeDlg.FormulaBox.Formula);
                Assert.AreEqual(BioMassCalc.MONOISOTOPIC.CalculateMassFromFormula(C12H12), editMoleculeDlg.FormulaBox.MonoMass ?? -1, massPrecisionTolerance);
                Assert.AreEqual(BioMassCalc.AVERAGE.CalculateMassFromFormula(C12H12), editMoleculeDlg.FormulaBox.AverageMass ?? -1, massPrecisionTolerance);
                editMoleculeDlg.FormulaBox.Formula     = null;
                editMoleculeDlg.Charge                 = 2; // If we set this after we set the mass, the mass will change since m/z is the actual input
                editMoleculeDlg.FormulaBox.AverageMass = 800;
                editMoleculeDlg.FormulaBox.MonoMass    = 805;
                editMoleculeDlg.NameText               = "Fragment";
            });
            OkDialog(editMoleculeDlg, editMoleculeDlg.OkDialog);
            var newdoc = WaitForDocumentChange(doc);

            Assert.AreEqual("Fragment", newdoc.MoleculeTransitions.ElementAt(0).Transition.CustomIon.ToString());
            Assert.AreEqual(BioMassCalc.CalculateIonMz(805, 2), newdoc.MoleculeTransitions.ElementAt(0).Mz, massPrecisionTolerance);
            Assert.IsFalse(ReferenceEquals(doc.MoleculeTransitions.ElementAt(0).Id, newdoc.MoleculeTransitions.ElementAt(0).Id)); // Changing the mass changes the Id

            // Verify that tree selection doesn't change just because we changed an ID object
            // (formerly the tree node would collapse and focus would jump up a level)
            RunUI(() =>
            {
                Assert.AreEqual(SkylineWindow.SequenceTree.SelectedNode, SkylineWindow.SequenceTree.Nodes[0].FirstNode.FirstNode.FirstNode);
            });

            // And test undo/redo
            RunUI(() => SkylineWindow.Undo());
            newdoc = WaitForDocumentChange(newdoc);
            Assert.AreNotEqual("Fragment", newdoc.MoleculeTransitions.ElementAt(0).Transition.CustomIon.ToString());
            Assert.AreNotEqual(BioMassCalc.CalculateIonMz(805, 2), newdoc.MoleculeTransitions.ElementAt(0).Mz, massPrecisionTolerance);
            Assert.IsTrue(ReferenceEquals(doc.MoleculeTransitions.ElementAt(0).Id, newdoc.MoleculeTransitions.ElementAt(0).Id));
            RunUI(() => SkylineWindow.Redo());
            newdoc = WaitForDocumentChange(newdoc);
            Assert.AreEqual("Fragment", newdoc.MoleculeTransitions.ElementAt(0).Transition.CustomIon.ToString());
            Assert.AreEqual(BioMassCalc.CalculateIonMz(805, 2), newdoc.MoleculeTransitions.ElementAt(0).Mz, massPrecisionTolerance);

            // Verify that tree selection doesn't change just because we changed an ID object
            // (formerly the tree node would collapse and focus would jump up a level)
            RunUI(() =>
            {
                Assert.AreEqual(SkylineWindow.SequenceTree.SelectedNode, SkylineWindow.SequenceTree.Nodes[0].FirstNode.FirstNode.FirstNode);
            });
        }
Ejemplo n.º 4
0
        private string GetTextFromMass(double?mass)
        {
            if (!mass.HasValue)
            {
                return(string.Empty);
            }
            double result = mass.Value;

            if (Charge.HasValue)
            {
                // We want to show this as an m/z value, rounded to a reasonable length
                result = SequenceMassCalc.PersistentMZ(BioMassCalc.CalculateIonMz(result, Charge.Value));
            }
            return(result.ToString(CultureInfo.CurrentCulture));
        }
Ejemplo n.º 5
0
        private void TestTaxolAdduct(string adductText, double expectedMz, int expectedCharge, HashSet <string> coverage)
        {
            // See http://fiehnlab.ucdavis.edu/staff/kind/Metabolomics/MS-Adduct-Calculator/
            var Taxol    = "C47H51NO14"; // M=853.33089 (agrees with chemspider)
            var calcMass = BioMassCalc.MONOISOTOPIC.CalculateMassFromFormula(Taxol);

            Assert.AreEqual(massTaxol, calcMass, .0001);
            var adduct = Adduct.FromStringAssumeProtonated(adductText);

            Assert.AreEqual(expectedCharge, adduct.AdductCharge);
            var mz = BioMassCalc.CalculateIonMz(calcMass, adduct);

            Assert.AreEqual(expectedMz, mz, .001);
            var massWithIsotopes = adduct.MassFromMz(mz, MassType.Monoisotopic);

            Assert.AreEqual(massTaxol, massWithIsotopes - adduct.GetIsotopesIncrementalMonoisotopicMass() / adduct.GetMassMultiplier(), .0001);
            coverage.Add(adduct.AsFormula());
        }
Ejemplo n.º 6
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);
        }
Ejemplo n.º 7
0
        public void AdductParserTest()
        {
            TestAdductOperators();

            var coverage = new HashSet <string>();

            TestPentaneAdduct("[M+2NH4]", "C5H20N2", 2, coverage);           // multiple of a group
            TestPentaneAdduct("[M+2(NH4)]", "C5H20N2", 2, coverage);         // multiple of a group in parenthesis
            TestPentaneAdduct("[M+2H]", "C5H14", 2, coverage);
            TestPentaneAdduct("[M2C13+2H]", "C3C'2H14", 2, coverage);        // Labeled
            TestPentaneAdduct("[2M2C13+2H]", "C6C'4H26", 2, coverage);       // Labeled dimer
            TestPentaneAdduct("[2M2C14+2H]", "C6C\"4H26", 2, coverage);      // Labeled dimer
            TestPentaneAdduct("[M2C13]", "C3C'2H12", 0, coverage);           // Labeled no charge
            TestPentaneAdduct("[2M2C13]", "C6C'4H24", 0, coverage);          // Labeled, dimer, no charge
            TestPentaneAdduct("[2M]", "C10H24", 0, coverage);                // dimer no charge
            TestPentaneAdduct("[2M2C13+3]", "C6C'4H24", 3, coverage);        // Labeled, dimer, charge only
            TestPentaneAdduct("[2M2C13]+3", "C6C'4H24", 3, coverage);        // Labeled, dimer, charge only
            TestPentaneAdduct("[2M2C13+++]", "C6C'4H24", 3, coverage);       // Labeled, dimer, charge only
            TestPentaneAdduct("[2M2C13]+++", "C6C'4H24", 3, coverage);       // Labeled, dimer, charge only
            TestPentaneAdduct("[2M+3]", "C10H24", 3, coverage);              // dimer charge only
            TestPentaneAdduct("[2M]+3", "C10H24", 3, coverage);              // dimer charge only
            TestPentaneAdduct("[2M+++]", "C10H24", 3, coverage);             // dimer charge only
            TestPentaneAdduct("[2M]+++", "C10H24", 3, coverage);             // dimer charge only
            TestPentaneAdduct("[2M2C13-3]", "C6C'4H24", -3, coverage);       // Labeled, dimer, charge only
            TestPentaneAdduct("[2M2C13---]", "C6C'4H24", -3, coverage);      // Labeled, dimer, charge only
            TestPentaneAdduct("[2M-3]", "C10H24", -3, coverage);             // dimer charge only
            TestPentaneAdduct("[2M---]", "C10H24", -3, coverage);            // dimer charge only
            TestPentaneAdduct("[2M2C133H2+2H]", "C6C'4H20H'6", 2, coverage); // Labeled with some complexity, multiplied
            TestPentaneAdduct("M+H", "C5H13", 1, coverage);
            TestPentaneAdduct("M+", PENTANE, 1, coverage);
            TestPentaneAdduct("M+2", PENTANE, 2, coverage);
            TestPentaneAdduct("M+3", PENTANE, 3, coverage);
            TestPentaneAdduct("M-", PENTANE, -1, coverage);
            TestPentaneAdduct("M-2", PENTANE, -2, coverage);
            TestPentaneAdduct("M-3", PENTANE, -3, coverage);
            TestPentaneAdduct("M++", PENTANE, 2, coverage);
            TestPentaneAdduct("M--", PENTANE, -2, coverage);
            TestPentaneAdduct("M", PENTANE, 0, coverage);           // Trivial non-adduct
            TestPentaneAdduct("M+CH3COO", "C7H15O2", -1, coverage); // From XCMS
            TestPentaneAdduct("[M+H]1+", "C5H13", 1, coverage);
            TestPentaneAdduct("[M-H]1-", "C5H11", -1, coverage);
            TestPentaneAdduct("[M-2H]", "C5H10", -2, coverage);
            TestPentaneAdduct("[M-2H]2-", "C5H10", -2, coverage);
            TestPentaneAdduct("[M+2H]++", "C5H14", 2, coverage);
            TestPentaneAdduct("[MH2+2H]++", "C5H13H'", 2, coverage);        // Isotope
            TestPentaneAdduct("[MH3+2H]++", "C5H13H\"", 2, coverage);       // Isotope
            TestPentaneAdduct("[MD+2H]++", "C5H13H'", 2, coverage);         // Isotope
            TestPentaneAdduct("[MD+DMSO+2H]++", "C7H19H'OS", 2, coverage);  // Check handling of Deuterium and DMSO together
            TestPentaneAdduct("[MT+DMSO+2H]++", "C7H19H\"OS", 2, coverage); // Check handling of Tritium
            TestPentaneAdduct("[M+DMSO+2H]++", "C7H20OS", 2, coverage);
            TestPentaneAdduct("[M+DMSO+2H]2+", "C7H20OS", 2, coverage);
            TestPentaneAdduct("[M+MeOH-H]", "C6H15O", -1, coverage); // Methanol "CH3OH"
            TestPentaneAdduct("[M+MeOX-H]", "C6H14N", -1, coverage); // Methoxamine "CH3N"
            TestPentaneAdduct("[M+TMS-H]", "C8H19Si", -1, coverage); // MSTFA(N-methyl-N-trimethylsilytrifluoroacetamide) "C3H8Si"
            TestPentaneAdduct("[M+TMS+MeOX]-", "C9H23NSi", -1, coverage);
            TestPentaneAdduct("[M+HCOO]", "C6H13O2", -1, coverage);
            TestPentaneAdduct("[M+NOS]5+", "C5H12NOS", 5, coverage);  // Not a real adduct, but be ready for adducts we just don't know about
            TestPentaneAdduct("[M+NOS]5", "C5H12NOS", 5, coverage);   // Not a real adduct, but be ready for adducts we just don't know about
            TestPentaneAdduct("[M+NOS]5-", "C5H12NOS", -5, coverage); // Not a real adduct, but be ready for adducts we just don't know about

            // See http://fiehnlab.ucdavis.edu/staff/kind/Metabolomics/MS-Adduct-Calculator/
            // There you will find an excel spreadsheet from which I pulled these numbers, which as it turns out has several errors in it.
            // There is also a table in the web page itself that contains the same values and some unmarked corrections.
            // Sadly that faulty spreadsheet is copied all over the internet.  I've let the author know what we found. - bspratt
            TestTaxolAdduct("M+3H", 285.450928, 3, coverage);
            TestTaxolAdduct("M+2H+Na", 292.778220, 3, coverage);
            TestTaxolAdduct("M+H+2Na", 300.105557, 3, coverage); // Spreadsheet and table both say 300.209820, but also says adduct "mass" = 15.766190, I get 15.6618987 using their H and Na masses (and this is clearly m/z, not mass)
            TestTaxolAdduct("M+3Na", 307.432848, 3, coverage);
            TestTaxolAdduct("M+2H", 427.672721, 2, coverage);
            TestTaxolAdduct("M+H+NH4", 436.185995, 2, coverage);
            TestTaxolAdduct("M+H+Na", 438.663692, 2, coverage);
            TestTaxolAdduct("M+H+K", 446.650662, 2, coverage);
            TestTaxolAdduct("M+ACN+2H", 448.185995, 2, coverage);
            TestTaxolAdduct("M+2Na", 449.654663, 2, coverage);
            TestTaxolAdduct("M+2ACN+2H", 468.699268, 2, coverage);
            TestTaxolAdduct("M+3ACN+2H", 489.212542, 2, coverage);
            TestTaxolAdduct("M+H", 854.338166, 1, coverage);
            TestTaxolAdduct("M+NH4", 871.364713, 1, coverage);
            TestTaxolAdduct("M+Na", 876.320108, 1, coverage);
            TestTaxolAdduct("M+CH3OH+H", 886.364379, 1, coverage);
            TestTaxolAdduct("M+K", 892.294048, 1, coverage);
            TestTaxolAdduct("M+ACN+H", 895.364713, 1, coverage);
            TestTaxolAdduct("M+2Na-H", 898.302050, 1, coverage);
            TestTaxolAdduct("M+IsoProp+H", 914.396230, 1, coverage);
            TestTaxolAdduct("M+ACN+Na", 917.346655, 1, coverage);
            TestTaxolAdduct("M+2K-H", 930.249930, 1, coverage);  // Spreadsheet and table disagree - spreadsheet says "M+2K+H" but that's 3+, not 1+, and this fits the mz value
            TestTaxolAdduct("M+DMSO+H", 932.352110, 1, coverage);
            TestTaxolAdduct("M+2ACN+H", 936.391260, 1, coverage);
            TestTaxolAdduct("M+IsoProp+Na+H", 468.692724, 2, coverage); // Spreadsheet and table both say mz=937.386000 z=1 (does Isoprop interact somehow to eliminate half the ionization?)
            TestTaxolAdduct("2M+H", 1707.669056, 1, coverage);
            TestTaxolAdduct("2M+NH4", 1724.695603, 1, coverage);
            TestTaxolAdduct("2M+Na", 1729.650998, 1, coverage);
            TestTaxolAdduct("2M+3H2O+2H", 881.354, 2, coverage); // Does not appear in table.  Charge agrees but spreadsheet says mz= 1734.684900
            TestTaxolAdduct("2M+K", 1745.624938, 1, coverage);
            TestTaxolAdduct("2M+ACN+H", 1748.695603, 1, coverage);
            TestTaxolAdduct("2M+ACN+Na", 1770.677545, 1, coverage);
            TestTaxolAdduct("M-3H", 283.436354, -3, coverage);
            TestTaxolAdduct("M-2H", 425.658169, -2, coverage);
            TestTaxolAdduct("M-H2O-H", 834.312500, -1, coverage);
            TestTaxolAdduct("M+-H2O-H", 834.312500, -1, coverage); // Tolerate empty atom description ("+-")
            TestTaxolAdduct("M-H", 852.323614, -1, coverage);
            TestTaxolAdduct("M+Na-2H", 874.305556, -1, coverage);
            TestTaxolAdduct("M+Cl", 888.300292, -1, coverage);
            TestTaxolAdduct("M+K-2H", 890.279496, -1, coverage);
            TestTaxolAdduct("M+FA-H", 898.329091, -1, coverage);
            TestTaxolAdduct("M+Hac-H", 912.344741, -1, coverage);
            TestTaxolAdduct("M+Br", 932.249775, -1, coverage);
            TestTaxolAdduct("MT+TFA-H", 968.324767, -1, coverage); // Tritium label + TFA
            TestTaxolAdduct("M+TFA-H", 966.316476, -1, coverage);
            TestTaxolAdduct("2M-H", 1705.654504, -1, coverage);
            TestTaxolAdduct("2M+FA-H", 1751.659981, -1, coverage);
            TestTaxolAdduct("2M+Hac-H", 1765.675631, -1, coverage);
            TestTaxolAdduct("3M-H", 2558.985394, -1, coverage); // Spreadsheet and table give mz as 2560.999946 -but also gives adduct "mass" as 1.007276, should be -1.007276

            // A couple more simple ones we support with statics
            TestTaxolAdduct("M+4H", 214.3400149, 4, coverage);
            TestTaxolAdduct("M+5H", 171.6734671, 5, coverage);

            // And a few of our own to exercise the interaction of multiplier and isotope
            var dC13 = BioMassCalc.MONOISOTOPIC.GetMass(BioMassCalc.C13) - BioMassCalc.MONOISOTOPIC.GetMass(BioMassCalc.C);

            TestTaxolAdduct("M2C13+3H", 285.450928 + (2 * dC13) / 3.0, 3, coverage);
            TestTaxolAdduct("M2C13+2H+Na", 292.778220 + (2 * dC13) / 3.0, 3, coverage);
            TestTaxolAdduct("2M2C13+3H", 285.450906 + (massTaxol + 4 * dC13) / 3.0, 3, coverage);
            TestTaxolAdduct("2M2C13+2H+Na", 292.778220 + (massTaxol + 4 * dC13) / 3.0, 3, coverage);
            var dC14 = BioMassCalc.MONOISOTOPIC.GetMass(BioMassCalc.C14) - BioMassCalc.MONOISOTOPIC.GetMass(BioMassCalc.C);

            TestTaxolAdduct("M2C14+3H", 285.450928 + (2 * dC14) / 3.0, 3, coverage);
            TestTaxolAdduct("M2C14+2H+Na", 292.778220 + (2 * dC14) / 3.0, 3, coverage);
            TestTaxolAdduct("2M2C14+3H", 285.450906 + (massTaxol + 4 * dC14) / 3.0, 3, coverage);
            TestTaxolAdduct("2M2C14+2H+Na", 292.778220 + (massTaxol + 4 * dC14) / 3.0, 3, coverage);

            // Using example adducts from
            // https://gnps.ucsd.edu/ProteoSAFe/gnpslibrary.jsp?library=GNPS-LIBRARY#%7B%22Library_Class_input%22%3A%221%7C%7C2%7C%7C3%7C%7CEXACT%22%7D
            var Hectochlorin     = "C27H34Cl2N2O9S2";
            var massHectochlorin = 664.108276; // http://www.chemspider.com/Chemical-Structure.552449.html?rid=3a7c08af-0886-4e82-9e4f-5211b8efb373
            var adduct           = Adduct.FromStringAssumeProtonated("M+H");
            var mol  = IonInfo.ApplyAdductToFormula(Hectochlorin, adduct).ToString();
            var mass = BioMassCalc.MONOISOTOPIC.CalculateMassFromFormula(mol);

            Assert.AreEqual(massHectochlorin + BioMassCalc.MONOISOTOPIC.CalculateMassFromFormula("H"), mass, 0.00001);
            var mz = BioMassCalc.CalculateIonMz(BioMassCalc.MONOISOTOPIC.CalculateMassFromFormula(Hectochlorin), adduct);

            Assert.AreEqual(665.11555415, mz, .000001);  // GNPS says 665.0 for Hectochlorin M+H
            mol = IonInfo.ApplyAdductToFormula(Hectochlorin, Adduct.FromStringAssumeProtonated("MCl37+H")).ToString();
            Assert.AreEqual("C27ClCl'H35N2O9S2", mol);
            mass = BioMassCalc.MONOISOTOPIC.CalculateMassFromFormula(mol);
            Assert.AreEqual(667.11315, mass, .00001);
            mol = IonInfo.ApplyAdductToFormula(Hectochlorin, Adduct.FromStringAssumeProtonated("M2Cl37+H")).ToString();
            Assert.AreEqual("C27Cl'2H35N2O9S2", mol);

            // Test ability to describe isotope label by mass only
            var heavy = Adduct.FromStringAssumeProtonated("2M1.2345+H");

            mz    = BioMassCalc.CalculateIonMz(new TypedMass(massHectochlorin, MassType.Monoisotopic), heavy);
            heavy = Adduct.FromStringAssumeProtonated("2M1.2345");
            mz    = BioMassCalc.CalculateIonMass(new TypedMass(massHectochlorin, MassType.Monoisotopic), heavy);
            Assert.AreEqual(2 * (massHectochlorin + 1.23456), mz, .001);
            heavy = Adduct.FromStringAssumeProtonated("M1.2345");
            mz    = BioMassCalc.CalculateIonMass(new TypedMass(massHectochlorin, MassType.Monoisotopic), heavy);
            Assert.AreEqual(massHectochlorin + 1.23456, mz, .001);
            heavy = Adduct.FromStringAssumeProtonated("2M(-1.2345)+H");
            mz    = BioMassCalc.CalculateIonMz(new TypedMass(massHectochlorin, MassType.Monoisotopic), heavy);
            Assert.AreEqual((2 * (massHectochlorin - 1.23456) + BioMassCalc.MONOISOTOPIC.CalculateMassFromFormula("H")), mz, .001);
            heavy = Adduct.FromStringAssumeProtonated("2M(-1.2345)");
            mz    = BioMassCalc.CalculateIonMass(new TypedMass(massHectochlorin, MassType.Monoisotopic), heavy);
            Assert.AreEqual(2 * (massHectochlorin - 1.23456), mz, .001);
            heavy = Adduct.FromStringAssumeProtonated("2M(1.2345)+H");
            mz    = BioMassCalc.CalculateIonMz(new TypedMass(massHectochlorin, MassType.Monoisotopic), heavy);
            Assert.AreEqual((2 * (massHectochlorin + 1.23456) + BioMassCalc.MONOISOTOPIC.CalculateMassFromFormula("H")), mz, .001);
            heavy = Adduct.FromStringAssumeProtonated("2M(1.2345)");
            mz    = BioMassCalc.CalculateIonMass(new TypedMass(massHectochlorin, MassType.Monoisotopic), heavy);
            Assert.AreEqual(2 * (massHectochlorin + 1.23456), mz, .001);

            TestException(Hectochlorin, "M3Cl37+H"); // Trying to label more chlorines than exist in the molecule
            TestException(Hectochlorin, "M-3Cl+H");  // Trying to remove more chlorines than exist in the molecule
            TestException(PENTANE, "M+foo+H");       // Unknown adduct
            TestException(PENTANE, "M2Cl37H+H");     // nonsense label ("2Cl37H2" would make sense, but regular H doesn't belong)
            TestException(PENTANE, "M+2H+");         // Trailing sign - we now understand this as a charge state declaration, but this one doesn't match described charge
            TestException(PENTANE, "[M-2H]3-");      // Declared charge doesn't match described charge

            // Test label stripping
            Assert.AreEqual("C5H9NO2S", (new IonInfo("C5H9H'3NO2S[M-3H]")).UnlabeledFormula);

            // Peptide representations
            Assert.AreEqual("C40H65N11O16", (new SequenceMassCalc(MassType.Average)).GetNeutralFormula("PEPTIDER", null));

            // Figuring out adducts from old style skyline doc ion molecules and ion precursors
            var adductDiff = Adduct.FromFormulaDiff("C6H27NO2Si2C'5", "C'5H11NO2", 3);

            Assert.AreEqual("[M+C6H16Si2]3+", adductDiff.AdductFormula);
            Assert.AreEqual(3, adductDiff.AdductCharge);
            Assert.AreEqual(Adduct.FromString("[M+C6H16Si2]3+", Adduct.ADDUCT_TYPE.non_proteomic, null), adductDiff);
            adductDiff = Adduct.FromFormulaDiff("C6H27NO2", "C6H27NO2", 3);
            Assert.AreEqual("[M+3]", adductDiff.AdductFormula);
            Assert.AreEqual(3, adductDiff.AdductCharge);
            adductDiff = Adduct.ProtonatedFromFormulaDiff("C6H27NO2Si2C'5", "C'5H11NO2", 3);
            var expectedFromProtonatedDiff = "[M+C6H13Si2+3H]";

            Assert.AreEqual(expectedFromProtonatedDiff, adductDiff.AdductFormula);
            Assert.AreEqual(3, adductDiff.AdductCharge);
            Assert.AreEqual(Adduct.FromString(expectedFromProtonatedDiff, Adduct.ADDUCT_TYPE.non_proteomic, null), adductDiff);
            adductDiff = Adduct.ProtonatedFromFormulaDiff("C6H27NO2", "C6H27NO2", 3);
            Assert.AreEqual("[M+3H]", adductDiff.AdductFormula);
            Assert.AreEqual(3, adductDiff.AdductCharge);

            // Implied positive mode
            TestPentaneAdduct("MH", "C5H13", 1, coverage);       // implied pos mode seems to be fairly common in the wild
            TestPentaneAdduct("MH+", "C5H13", 1, coverage);      // implied pos mode seems to be fairly common in the wild
            TestPentaneAdduct("MNH4", "C5H16N", 1, coverage);    // implied pos mode seems to be fairly common in the wild
            TestPentaneAdduct("MNH4+", "C5H16N", 1, coverage);   // implied pos mode seems to be fairly common in the wild
            TestPentaneAdduct("2MNH4+", "C10H28N", 1, coverage); // implied pos mode seems to be fairly common in the wild

            // Explict charge states within the adduct
            TestPentaneAdduct("[M+S+]", "C5H12S", 1, coverage);   // We're trusting the user to declare charge
            TestPentaneAdduct("[3M+S+]", "C15H36S", 1, coverage); // We're trusting the user to declare charge
            TestPentaneAdduct("[M+S++]", "C5H12S", 2, coverage);  // We're trusting the user to declare charge
            TestPentaneAdduct("[MS+]", "C5H12S", 1, coverage);    // We're trusting the user to declare charge
            TestPentaneAdduct("[MS++]", "C5H12S", 2, coverage);   // We're trusting the user to declare charge
            TestPentaneAdduct("[M+S+2]", "C5H12S", 2, coverage);  // We're trusting the user to declare charge
            TestPentaneAdduct("[M+S]2+", "C5H12S", 2, coverage);  // We're trusting the user to declare charge
            TestPentaneAdduct("[M+S-]", "C5H12S", -1, coverage);  // We're trusting the user to declare charge
            TestPentaneAdduct("[M+S--]", "C5H12S", -2, coverage); // We're trusting the user to declare charge
            TestPentaneAdduct("[M-3H-3]", "C5H9", -3, coverage);  // We're trusting the user to declare charge
            TestPentaneAdduct("[M+S-2]", "C5H12S", -2, coverage); // We're trusting the user to declare charge
            TestPentaneAdduct("[M+S]2-", "C5H12S", -2, coverage); // We're trusting the user to declare charge

            // Did we test all the adducts we claim to support?
            foreach (var adducts in new[] {
                Adduct.DEFACTO_STANDARD_ADDUCTS,
                Adduct.COMMON_CHARGEONLY_ADDUCTS,
                Adduct.COMMON_SMALL_MOL_ADDUCTS.Select(a => a.AdductFormula),
                Adduct.COMMON_PROTONATED_ADDUCTS.Select(a => a.AdductFormula),
            })
            {
                foreach (var adductText in adducts)
                {
                    if (!coverage.Contains(adductText))
                    {
                        Assert.Fail("Need to add a test for adduct {0}", adductText);
                    }
                }
            }
        }
        public void OkDialog()
        {
            var helper = new MessageBoxHelper(this);
            var charge = 0;

            if (textCharge.Visible && !helper.ValidateSignedNumberTextBox(textCharge, _minCharge, _maxCharge, out charge))
            {
                return;
            }
            if (RetentionTimeWindow.HasValue && !RetentionTime.HasValue)
            {
                helper.ShowTextBoxError(textRetentionTimeWindow,
                                        Resources.Peptide_ExplicitRetentionTimeWindow_Explicit_retention_time_window_requires_an_explicit_retention_time_value_);
                return;
            }
            Charge = charge; // Note: order matters here, this settor indirectly updates _formulaBox.MonoMass when formula is empty
            if (string.IsNullOrEmpty(_formulaBox.Formula))
            {
                // Can the text fields be understood as mz?
                if (!_formulaBox.ValidateAverageText(helper))
                {
                    return;
                }
                if (!_formulaBox.ValidateMonoText(helper))
                {
                    return;
                }
            }
            var formula     = _formulaBox.Formula;
            var monoMass    = _formulaBox.MonoMass ?? 0;
            var averageMass = _formulaBox.AverageMass ?? 0;

            if (monoMass < CustomIon.MIN_MASS || averageMass < CustomIon.MIN_MASS)
            {
                _formulaBox.ShowTextBoxErrorFormula(helper,
                                                    string.Format(Resources.EditCustomMoleculeDlg_OkDialog_Custom_molecules_must_have_a_mass_greater_than_or_equal_to__0__,
                                                                  CustomIon.MIN_MASS));
                return;
            }
            if (monoMass > CustomIon.MAX_MASS || averageMass > CustomIon.MAX_MASS)
            {
                _formulaBox.ShowTextBoxErrorFormula(helper,
                                                    string.Format(Resources.EditCustomMoleculeDlg_OkDialog_Custom_molecules_must_have_a_mass_less_than_or_equal_to__0__, CustomIon.MAX_MASS));
                return;
            }

            if ((_transitionSettings != null) &&
                (!_transitionSettings.IsMeasurablePrecursor(BioMassCalc.CalculateIonMz(monoMass, charge)) ||
                 !_transitionSettings.IsMeasurablePrecursor(BioMassCalc.CalculateIonMz(averageMass, charge))))
            {
                _formulaBox.ShowTextBoxErrorFormula(helper, Resources.SkylineWindow_AddMolecule_The_precursor_m_z_for_this_molecule_is_out_of_range_for_your_instrument_settings_);
                return;
            }
            if (!string.IsNullOrEmpty(_formulaBox.Formula))
            {
                try
                {
                    ResultCustomIon = new DocNodeCustomIon(formula, textName.Text);
                }
                catch (InvalidDataException x)
                {
                    _formulaBox.ShowTextBoxErrorFormula(helper, x.Message);
                    return;
                }
            }
            else
            {
                ResultCustomIon = new DocNodeCustomIon(monoMass, averageMass, textName.Text);
            }
            // Did user change the list of heavy labels?
            if (_driverLabelType != null)
            {
                PeptideModifications modifications = new PeptideModifications(
                    _peptideSettings.Modifications.StaticModifications,
                    _peptideSettings.Modifications.MaxVariableMods,
                    _peptideSettings.Modifications.MaxNeutralLosses,
                    _driverLabelType.GetHeavyModifications(), // This is the only thing the user may have altered
                    _peptideSettings.Modifications.InternalStandardTypes);
                var settings = _peptideSettings.ChangeModifications(modifications);
                // Only update if anything changed
                if (!Equals(settings, _peptideSettings))
                {
                    SrmSettings newSettings = _parent.DocumentUI.Settings.ChangePeptideSettings(settings);
                    if (!_parent.ChangeSettings(newSettings, true))
                    {
                        return;
                    }
                    _peptideSettings = newSettings.PeptideSettings;
                }
            }

            // See if this combination of charge and label would conflict with any existing transition groups
            if (_existingIds != null && _existingIds.Any(t =>
            {
                var transitionGroup = t as TransitionGroup;
                return(transitionGroup != null && Equals(transitionGroup.LabelType, IsotopeLabelType) &&
                       Equals(transitionGroup.PrecursorCharge, Charge) && !ReferenceEquals(t, _initialId));
            }))
            {
                helper.ShowTextBoxError(textName,
                                        Resources.EditCustomMoleculeDlg_OkDialog_A_precursor_with_that_charge_and_label_type_already_exists_, textName.Text);
                return;
            }

            // See if this would conflict with any existing transitions
            if (_existingIds != null && (_existingIds.Any(t =>
            {
                var transition = t as Transition;
                return(transition != null && ((transition.Charge == Charge) && Equals(transition.CustomIon, ResultCustomIon)) && !ReferenceEquals(t, _initialId));
            })))
            {
                helper.ShowTextBoxError(textName,
                                        Resources.EditCustomMoleculeDlg_OkDialog_A_similar_transition_already_exists_, textName.Text);
                return;
            }
            DialogResult = DialogResult.OK;
        }
Ejemplo n.º 9
0
        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);
        }
Ejemplo n.º 10
0
        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;
        }
Ejemplo n.º 11
0
        private static void TestEditingTransition()
        {
            double massPrecisionTolerance = Math.Pow(10, -SequenceMassCalc.MassPrecision);
            var    doc = SkylineWindow.Document;

            RunUI(() =>
            {
                SkylineWindow.ExpandPrecursors();
                SkylineWindow.SequenceTree.SelectedNode = SkylineWindow.SequenceTree.Nodes[0].FirstNode.FirstNode.FirstNode;
            });
            var editMoleculeDlg =
                ShowDialog <EditCustomMoleculeDlg>(
                    () => SkylineWindow.ModifyTransition((TransitionTreeNode)SkylineWindow.SequenceTree.SelectedNode));
            var monoMass = new TypedMass(805, MassType.Monoisotopic);

            RunUI(() =>
            {
                Assert.AreEqual(C12H12 + Adduct.NonProteomicProtonatedFromCharge(1).AdductFormula, editMoleculeDlg.FormulaBox.Formula);
                Assert.AreEqual(BioMassCalc.MONOISOTOPIC.CalculateMassFromFormula(C12H12),
                                editMoleculeDlg.FormulaBox.MonoMass ?? -1, massPrecisionTolerance);
                Assert.AreEqual(BioMassCalc.AVERAGE.CalculateMassFromFormula(C12H12),
                                editMoleculeDlg.FormulaBox.AverageMass ?? -1, massPrecisionTolerance);
                Assert.AreEqual(Adduct.NonProteomicProtonatedFromCharge(1).AdductFormula, editMoleculeDlg.FormulaBox.Adduct.AdductFormula);
                editMoleculeDlg.FormulaBox.Formula = editMoleculeDlg.FormulaBox.Adduct.AdductFormula; // Remove neutral formula, should leave masses unchanged
                Assert.AreEqual(BioMassCalc.MONOISOTOPIC.CalculateMassFromFormula(C12H12),
                                editMoleculeDlg.FormulaBox.MonoMass ?? -1, massPrecisionTolerance);
                Assert.AreEqual(BioMassCalc.AVERAGE.CalculateMassFromFormula(C12H12),
                                editMoleculeDlg.FormulaBox.AverageMass ?? -1, massPrecisionTolerance);
                editMoleculeDlg.FormulaBox.AverageMass = 800;
                editMoleculeDlg.FormulaBox.MonoMass    = monoMass.Value;
                editMoleculeDlg.NameText = "Fragment";
            });
            OkDialog(editMoleculeDlg, editMoleculeDlg.OkDialog);
            var newdoc = WaitForDocumentChange(doc);

            Assert.AreEqual("Fragment", newdoc.MoleculeTransitions.ElementAt(0).Transition.CustomIon.ToString());
            Assert.AreEqual(BioMassCalc.CalculateIonMz(monoMass, editMoleculeDlg.Adduct), newdoc.MoleculeTransitions.ElementAt(0).Mz, massPrecisionTolerance);
            Assert.IsFalse(ReferenceEquals(doc.MoleculeTransitions.ElementAt(0).Id, newdoc.MoleculeTransitions.ElementAt(0).Id)); // Changing the mass changes the Id

            // Verify that tree selection doesn't change just because we changed an ID object
            // (formerly the tree node would collapse and focus would jump up a level)
            RunUI(() =>
            {
                Assert.AreEqual(SkylineWindow.SequenceTree.SelectedNode, SkylineWindow.SequenceTree.Nodes[0].FirstNode.FirstNode.FirstNode);
            });

            // And test undo/redo
            RunUI(() => SkylineWindow.Undo());
            newdoc = WaitForDocumentChange(newdoc);
            Assert.AreNotEqual("Fragment", newdoc.MoleculeTransitions.ElementAt(0).Transition.CustomIon.ToString());
            Assert.AreNotEqual(BioMassCalc.CalculateIonMz(monoMass, editMoleculeDlg.Adduct), newdoc.MoleculeTransitions.ElementAt(0).Mz, massPrecisionTolerance);
            Assert.IsTrue(ReferenceEquals(doc.MoleculeTransitions.ElementAt(0).Id, newdoc.MoleculeTransitions.ElementAt(0).Id));
            RunUI(() => SkylineWindow.Redo());
            newdoc = WaitForDocumentChange(newdoc);
            Assert.AreEqual("Fragment", newdoc.MoleculeTransitions.ElementAt(0).Transition.CustomIon.ToString());
            Assert.AreEqual(BioMassCalc.CalculateIonMz(monoMass, editMoleculeDlg.Adduct), newdoc.MoleculeTransitions.ElementAt(0).Mz, massPrecisionTolerance);

            // Verify that tree selection doesn't change just because we changed an ID object
            // (formerly the tree node would collapse and focus would jump up a level)
            RunUI(() =>
            {
                Assert.AreEqual(SkylineWindow.SequenceTree.SelectedNode, SkylineWindow.SequenceTree.Nodes[0].FirstNode.FirstNode.FirstNode);
            });
        }
Ejemplo n.º 12
0
        protected override void DoTest()
        {
            const double precursorMzAtZNeg2 = 242.0373281;
            const double productMzAtZNeg2   = 213.5097436;
            const double precursorCE        = 1.23;
            const double precursorDT        = 2.34;
            const double highEnergyDtOffset = -.012;
            const double slens                 = 6.789;
            const double coneVoltage           = 7.89;
            const double compensationVoltage   = 8.901;
            const double declusteringPotential = 9.012;
            const double precursorRT           = 3.45;
            const double precursorRTWindow     = 4.567;
            const string note = "noted!";

            var docEmpty = SkylineWindow.Document;

            TestLabelsNoFormulas();
            TestPrecursorTransitions();
            TestTransitionListArrangementAndReporting();

            string line1 = "MyMolecule\tMyMol\tMyFrag\tC34H12O4\tC34H3O\t" + precursorMzAtZNeg2 + "\t" + productMzAtZNeg2 + "\t-2\t-2\tlight\t" +
                           precursorRT + "\t" + precursorRTWindow + "\t" + precursorCE + "\t" + note + "\t" + precursorDT + "\t" + highEnergyDtOffset + "\t" + slens + "\t" + coneVoltage +
                           "\t" + compensationVoltage + "\t" + declusteringPotential; // Legit
            const string line2start = "\r\nMyMolecule2\tMyMol2\tMyFrag2\tCH12O4\tCH3O\t";
            const string line3      = "\r\nMyMolecule2\tMyMol2\tMyFrag2\tCH12O4\tCHH500000000\t\t\t1\t1";
            const string line4      = "\r\nMyMolecule3\tMyMol3\tMyFrag3\tH2\tH\t\t\t1\t1";

            // Provoke some errors
            TestError(line1.Replace("C34H12O4", "C77H12O4"), // mz and formula disagree
                      String.Format(Resources.PasteDlg_ReadPrecursorOrProductColumns_Error_on_line__0___Precursor_m_z__1__does_not_agree_with_value__2__as_calculated_from_ion_formula_and_charge_state__delta____3___Transition_Settings___Instrument___Method_match_tolerance_m_z____4_____Correct_the_m_z_value_in_the_table__or_leave_it_blank_and_Skyline_will_calculate_it_for_you_,
                                    1, (float)precursorMzAtZNeg2, 500.0373, 258, docEmpty.Settings.TransitionSettings.Instrument.MzMatchTolerance));
            TestError(line1.Replace("C34H3", "C76H3"), // mz and formula disagree
                      String.Format(Resources.PasteDlg_ReadPrecursorOrProductColumns_Error_on_line__0___Product_m_z__1__does_not_agree_with_value__2__as_calculated_from_ion_formula_and_charge_state__delta____3___Transition_Settings___Instrument___Method_match_tolerance_m_z____4_____Correct_the_m_z_value_in_the_table__or_leave_it_blank_and_Skyline_will_calculate_it_for_you_,
                                    1, (float)productMzAtZNeg2, 465.5097, 252, docEmpty.Settings.TransitionSettings.Instrument.MzMatchTolerance));
            var badcharge = Transition.MAX_PRODUCT_CHARGE + 1;

            TestError(line1 + line2start + "\t\t1\t" + badcharge, // Excessively large charge for product
                      String.Format(Resources.Transition_Validate_Product_ion_charge__0__must_be_non_zero_and_between__1__and__2__,
                                    badcharge, -Transition.MAX_PRODUCT_CHARGE, Transition.MAX_PRODUCT_CHARGE));
            badcharge = 120;
            TestError(line1 + line2start + "\t\t" + badcharge + "\t1", // Insanely large charge for precursor
                      String.Format(Resources.Transition_Validate_Precursor_charge__0__must_be_non_zero_and_between__1__and__2__,
                                    badcharge, -TransitionGroup.MAX_PRECURSOR_CHARGE, TransitionGroup.MAX_PRECURSOR_CHARGE));
            TestError(line1 + line2start + "\t\t1\t",                                                                                                                                                  // No mz or charge for product
                      String.Format(Resources.PasteDlg_ValidateEntry_Error_on_line__0___Product_needs_values_for_any_two_of__Formula__m_z_or_Charge_, 2));
            TestError(line1 + line2start + "19\t5",                                                                                                                                                    // Precursor Formula and m/z don't make sense together
                      String.Format(Resources.PasteDlg_ValidateEntry_Error_on_line__0___Precursor_formula_and_m_z_value_do_not_agree_for_any_charge_state_, 2));
            TestError(line1 + line2start + "\t5\t1",                                                                                                                                                   // Product Formula and m/z don't make sense together
                      String.Format(Resources.PasteDlg_ValidateEntry_Error_on_line__0___Product_formula_and_m_z_value_do_not_agree_for_any_charge_state_, 2));
            TestError(line1 + line2start + "\t",                                                                                                                                                       // No mz or charge for precursor or product
                      String.Format(Resources.PasteDlg_ValidateEntry_Error_on_line__0___Precursor_needs_values_for_any_two_of__Formula__m_z_or_Charge_, 2));
            TestError(line1 + line3,                                                                                                                                                                   // Insanely large molecule
                      string.Format(Resources.CustomIon_Validate_The_mass_of_the_custom_ion_exceeeds_the_maximum_of__0_, CustomIon.MAX_MASS));
            TestError(line1 + line4,                                                                                                                                                                   // Insanely small molecule
                      string.Format(Resources.CustomIon_Validate_The_mass_of_the_custom_ion_is_less_than_the_minimum_of__0__, CustomIon.MIN_MASS));
            TestError(line1 + line2start + +precursorMzAtZNeg2 + "\t" + productMzAtZNeg2 + "\t-2\t-2\t\t\t" + precursorRTWindow + "\t" + precursorCE + "\t" + precursorDT + "\t" + highEnergyDtOffset, // Explicit retention time window without retention time
                      Resources.Peptide_ExplicitRetentionTimeWindow_Explicit_retention_time_window_requires_an_explicit_retention_time_value_);
            for (int withSpecials = 2; withSpecials-- > 0;)
            {
                // By default we don't show drift or other exotic columns
                var columnOrder = (withSpecials == 0) ? null : new[]
                {
                    PasteDlg.SmallMoleculeTransitionListColumnHeaders.moleculeGroup,
                    PasteDlg.SmallMoleculeTransitionListColumnHeaders.namePrecursor,
                    PasteDlg.SmallMoleculeTransitionListColumnHeaders.nameProduct,
                    PasteDlg.SmallMoleculeTransitionListColumnHeaders.formulaPrecursor,
                    PasteDlg.SmallMoleculeTransitionListColumnHeaders.formulaProduct,
                    PasteDlg.SmallMoleculeTransitionListColumnHeaders.mzPrecursor,
                    PasteDlg.SmallMoleculeTransitionListColumnHeaders.mzProduct,
                    PasteDlg.SmallMoleculeTransitionListColumnHeaders.chargePrecursor,
                    PasteDlg.SmallMoleculeTransitionListColumnHeaders.chargeProduct,
                    PasteDlg.SmallMoleculeTransitionListColumnHeaders.labelType,
                    PasteDlg.SmallMoleculeTransitionListColumnHeaders.rtPrecursor,
                    PasteDlg.SmallMoleculeTransitionListColumnHeaders.rtWindowPrecursor,
                    PasteDlg.SmallMoleculeTransitionListColumnHeaders.cePrecursor,
                    PasteDlg.SmallMoleculeTransitionListColumnHeaders.note,
                    PasteDlg.SmallMoleculeTransitionListColumnHeaders.dtPrecursor,
                    PasteDlg.SmallMoleculeTransitionListColumnHeaders.dtHighEnergyOffset,
                    PasteDlg.SmallMoleculeTransitionListColumnHeaders.slens,
                    PasteDlg.SmallMoleculeTransitionListColumnHeaders.coneVoltage,
                    PasteDlg.SmallMoleculeTransitionListColumnHeaders.compensationVoltage,
                    PasteDlg.SmallMoleculeTransitionListColumnHeaders.declusteringPotential,
                };
                // Take a legit full paste and mess with each field in turn
                string[] fields         = { "MyMol", "MyPrecursor", "MyProduct", "C12H9O4", "C6H4O2", "217.049535420091", "108.020580420091", "1", "1", "heavy", "123", "5", "25", "this is a note", "7", "9", "88.5", "99.6", "77.3", "66.2" };
                string[] badfields      = { "", "", "", "123", "123", "fish", "-345", "cat", "pig", "12", "frog", "hamster", "boston", "foosball", "greasy", "mumble", "gumdrop", "dingle", "gorse", "AHHHHHRGH" };
                var      expectedErrors = new List <string>()
                {
                    Resources.PasteDlg_ShowNoErrors_No_errors, Resources.PasteDlg_ShowNoErrors_No_errors, Resources.PasteDlg_ShowNoErrors_No_errors,  // No name, no problem
                    BioMassCalc.MONOISOTOPIC.FormatArgumentExceptionMessage(badfields[3]),
                    BioMassCalc.MONOISOTOPIC.FormatArgumentExceptionMessage(badfields[4]),
                    string.Format(Resources.PasteDlg_ReadPrecursorOrProductColumns_Invalid_m_z_value__0_, badfields[5]),
                    string.Format(Resources.PasteDlg_ReadPrecursorOrProductColumns_Invalid_m_z_value__0_, badfields[6]),
                    string.Format(Resources.PasteDlg_ReadPrecursorOrProductColumns_Invalid_charge_value__0_, badfields[7]),
                    string.Format(Resources.PasteDlg_ReadPrecursorOrProductColumns_Invalid_charge_value__0_, badfields[8]),
                    string.Format(Resources.SrmDocument_ReadLabelType_The_isotope_modification_type__0__does_not_exist_in_the_document_settings, badfields[9]),
                    string.Format(Resources.PasteDlg_ReadPrecursorOrProductColumns_Invalid_retention_time_value__0_, badfields[10]),
                    string.Format(Resources.PasteDlg_ReadPrecursorOrProductColumns_Invalid_retention_time_window_value__0_, badfields[11]),
                    string.Format(Resources.PasteDlg_ReadPrecursorOrProductColumns_Invalid_collision_energy_value__0_, badfields[12]),
                    null // Notes are freeform, so any value is fine
                };
                if (withSpecials > 0)
                {
                    var s = expectedErrors.Count;
                    expectedErrors.Add(
                        string.Format(Resources.PasteDlg_ReadPrecursorOrProductColumns_Invalid_drift_time_value__0_, badfields[s++]));
                    expectedErrors.Add(
                        string.Format(Resources.PasteDlg_ReadPrecursorOrProductColumns_Invalid_drift_time_high_energy_offset_value__0_, badfields[s++]));
                    expectedErrors.Add(
                        string.Format(Resources.PasteDlg_ReadPrecursorOrProductColumns_Invalid_S_Lens_value__0_, badfields[s++]));
                    expectedErrors.Add(
                        string.Format(Resources.PasteDlg_ReadPrecursorOrProductColumns_Invalid_cone_voltage_value__0_, badfields[s++]));
                    expectedErrors.Add(
                        string.Format(Resources.PasteDlg_ReadPrecursorOrProductColumns_Invalid_compensation_voltage__0_, badfields[s++]));
                    expectedErrors.Add(
                        string.Format(Resources.PasteDlg_ReadPrecursorOrProductColumns_Invalid_declustering_potential__0_, badfields[s++]));
                }
                expectedErrors.Add(Resources.PasteDlg_ShowNoErrors_No_errors); // N+1'th pass is unadulterated
                for (var bad = 0; bad < expectedErrors.Count(); bad++)
                {
                    var line = "";
                    for (var f = 0; f < expectedErrors.Count() - 1; f++)
                    {
                        line += ((bad == f) ? badfields[f] : fields[f]).Replace(".", LocalizationHelper.CurrentCulture.NumberFormat.NumberDecimalSeparator) + "\t";
                    }
                    if (!string.IsNullOrEmpty(expectedErrors[bad]))
                    {
                        TestError(line, expectedErrors[bad], columnOrder);
                    }
                }
            }

            // Now load the document with a legit paste
            TestError(line1 + line2start + "\t\t1\t1", String.Empty);
            var docOrig = WaitForDocumentChange(docEmpty);
            var testTransitionGroups = docOrig.MoleculeTransitionGroups.ToArray();

            Assert.AreEqual(2, testTransitionGroups.Count());
            var transitionGroup = testTransitionGroups[0];
            var precursor       = docOrig.Molecules.First();
            var product         = transitionGroup.Transitions.First();

            Assert.AreEqual(precursorCE, transitionGroup.ExplicitValues.CollisionEnergy);
            Assert.AreEqual(precursorDT, transitionGroup.ExplicitValues.DriftTimeMsec);
            Assert.AreEqual(slens, transitionGroup.ExplicitValues.SLens);
            Assert.AreEqual(coneVoltage, transitionGroup.ExplicitValues.ConeVoltage);
            Assert.AreEqual(compensationVoltage, transitionGroup.ExplicitValues.CompensationVoltage);
            Assert.AreEqual(declusteringPotential, transitionGroup.ExplicitValues.DeclusteringPotential);
            Assert.AreEqual(note, product.Annotations.Note);
            Assert.AreEqual(highEnergyDtOffset, transitionGroup.ExplicitValues.DriftTimeHighEnergyOffsetMsec.Value, 1E-7);
            Assert.AreEqual(precursorRT, precursor.ExplicitRetentionTime.RetentionTime);
            Assert.AreEqual(precursorRTWindow, precursor.ExplicitRetentionTime.RetentionTimeWindow);
            Assert.IsTrue(ReferenceEquals(transitionGroup.TransitionGroup, product.Transition.Group));
            Assert.AreEqual(precursorMzAtZNeg2, BioMassCalc.CalculateIonMz(precursor.CustomIon.MonoisotopicMass, transitionGroup.PrecursorCharge), 1E-7);
            Assert.AreEqual(productMzAtZNeg2, BioMassCalc.CalculateIonMz(product.GetIonMass(), product.Transition.Charge), 1E-7);
            // Does that produce the expected transition list file?
            TestTransitionListOutput(docOrig, "PasteMoleculeTinyTest.csv", "PasteMoleculeTinyTestExpected.csv", ExportFileType.IsolationList);
            // Does serialization of imported values work properly?
            AssertEx.Serializable(docOrig);

            // Reset
            RunUI(() =>
            {
                SkylineWindow.NewDocument(true);
                docOrig = SkylineWindow.Document;
            });

            // Now a proper user data set
            var      pasteDlg = ShowDialog <PasteDlg>(SkylineWindow.ShowPasteTransitionListDlg);
            PasteDlg dlg      = pasteDlg;

            RunUI(() =>
            {
                dlg.IsMolecule = true;
                dlg.SetSmallMoleculeColumns(null); // Reset column headers to default selection and order
            });
            // Formerly SetExcelFileClipboardText(TestFilesDir.GetTestPath("MoleculeTransitionList.xlsx"),"sheet1",6,false); but TeamCity doesn't like that
            SetCsvFileClipboardText(TestFilesDir.GetTestPath("MoleculeTransitionList.csv"));
            RunUI(pasteDlg.PasteTransitions);
            OkDialog(pasteDlg, pasteDlg.OkDialog);
            var pastedDoc = WaitForDocumentChange(docOrig);

            AssertEx.Serializable(pastedDoc);
            RunUI(() => SkylineWindow.SaveDocument(TestFilesDir.GetTestPath("PasteMolecules.Sky")));

            var paths = new []
            {
                "13417_02_WAA283_3805_071514" + ExtensionTestContext.ExtWatersRaw,
                "13417_03_WAA283_3805_071514" + ExtensionTestContext.ExtWatersRaw,
                "13418_02_WAA283_3805_071514" + ExtensionTestContext.ExtWatersRaw,
                "13418_03_WAA283_3805_071514" + ExtensionTestContext.ExtWatersRaw,
            };
            var doc = SkylineWindow.Document;

            RunUI(() => SkylineWindow.ChangeSettings(doc.Settings.
                                                     ChangeTransitionSettings(
                                                         doc.Settings.TransitionSettings.ChangeInstrument(
                                                             doc.Settings.TransitionSettings.Instrument.ChangeMzMatchTolerance(0.6))), true));
            PasteMoleculesTestImportResults(paths);
            var importDoc       = SkylineWindow.Document;
            var transitionCount = importDoc.MoleculeTransitionCount;
            var tranWithResults = importDoc.MoleculeTransitions.Count(tran => tran.HasResults);
            var tranWithPeaks   = importDoc.MoleculeTransitions.Count(tran =>
            {
                for (int i = 0; i < 4; i++)
                {
                    if (tran.GetPeakCountRatio(i) > 0)
                    {
                        return(true);
                    }
                }
                return(false);
            });

            // PauseTest(); // Pretty pictures!
            Assert.AreEqual(98, transitionCount);
            Assert.AreEqual(98, tranWithResults);
            Assert.AreEqual(90, tranWithPeaks);

            // Does that produce the expected transition list file?
            TestTransitionListOutput(importDoc, "PasteMoleculeTest.csv", "PasteMoleculeTestExpected.csv", ExportFileType.List);

            // Verify that MS1 filtering works properly
            var pasteText =
                "Steryl esters [ST0102],12:0 Cholesteryl ester,C39H68O2NH4,1\r\n" +
                "Steryl esters [ST0102],14:0 Cholesteryl ester,C41H72O2NH4,1\r\n" +
                "Steryl esters [ST0102],14:1 Cholesteryl ester,C41H70O2NH4,1\r\n" +
                "Steryl esters [ST0102],15:1 Cholesteryl ester,C42H72O2NH4,1";

            var columnOrderB = new[]
            {
                PasteDlg.SmallMoleculeTransitionListColumnHeaders.moleculeGroup,
                PasteDlg.SmallMoleculeTransitionListColumnHeaders.namePrecursor,
                PasteDlg.SmallMoleculeTransitionListColumnHeaders.formulaPrecursor,
                PasteDlg.SmallMoleculeTransitionListColumnHeaders.chargePrecursor,
            };

            // Doc is set for MS1 filtering, precursor transitions, charge=1, two peaks, should show M and M+1, M+2 after filter is invoked by changing to 3 peaks
            RunUI(() => SkylineWindow.OpenFile(TestFilesDir.GetTestPath("small_molecule_missing_m1.sky")));
            WaitForDocumentLoaded();
            TestError(pasteText, String.Empty, columnOrderB);
            var docB = SkylineWindow.Document;

            Assert.AreEqual(4, docB.MoleculeTransitionCount); // Initial import is faithful to what's pasted

            var transitionSettingsUI = ShowDialog <TransitionSettingsUI>(SkylineWindow.ShowTransitionSettingsUI);

            RunUI(() =>
            {
                transitionSettingsUI.SelectedTab = TransitionSettingsUI.TABS.FullScan;
                transitionSettingsUI.Peaks       = 3;
            });
            OkDialog(transitionSettingsUI, transitionSettingsUI.OkDialog);
            docB = WaitForDocumentChange(docB);
            Assert.AreEqual(12, docB.MoleculeTransitionCount);

            // Verify that we can import heavy/light pairs
            var columnOrderC = new[]
            {
                PasteDlg.SmallMoleculeTransitionListColumnHeaders.moleculeGroup,
                PasteDlg.SmallMoleculeTransitionListColumnHeaders.namePrecursor,
                PasteDlg.SmallMoleculeTransitionListColumnHeaders.formulaPrecursor,
                PasteDlg.SmallMoleculeTransitionListColumnHeaders.mzProduct,
                PasteDlg.SmallMoleculeTransitionListColumnHeaders.chargePrecursor,
                PasteDlg.SmallMoleculeTransitionListColumnHeaders.chargeProduct,
                PasteDlg.SmallMoleculeTransitionListColumnHeaders.labelType,
            };

            pasteText =
                "A,27-HC,C36H57N2O3,135,1,1,light\r\n" +
                "A,27-HC,C36H57N2O3,181,1,1,light\r\n" +
                "A,27-HC,C36H57N2O3,367,1,1,light\r\n" +
                "A,27-HC,C36H51H'6N2O3,135,1,1,heavy\r\n" +
                "A,27-HC,C36H51H'6N2O3,181,1,1,heavy\r\n" +
                "A,27-HC,C36H51H'6N2O3,215,1,1,heavy\r\n";
            RunUI(() =>
            {
                SkylineWindow.NewDocument(true);
            });
            TestError(pasteText, String.Empty, columnOrderC);
            var docC = SkylineWindow.Document;

            Assert.AreEqual(1, docC.MoleculeGroupCount);
            Assert.AreEqual(1, docC.MoleculeCount);
            Assert.AreEqual(2, docC.MoleculeTransitionGroupCount);
        }
Ejemplo n.º 13
0
        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));
                }
            }
        }
Ejemplo n.º 14
0
        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);
                            }
                        }
                    }
                }
            }
        }
Ejemplo n.º 15
0
        public void ReporterIonTest()
        {
            // Test the code that updates old-style formulas
            Assert.AreEqual("C5C'H13N2", BioMassCalc.AddH("C5C'H12N2"));
            Assert.AreEqual("CO2H", BioMassCalc.AddH("CO2"));

            var          docOriginal = new SrmDocument(SrmSettingsList.GetDefault().ChangeTransitionInstrument(instrument => instrument.ChangeMinMz(10))); // H2O2 is not very heavy!
            IdentityPath path;
            SrmDocument  docPeptide = docOriginal.ImportFasta(new StringReader(">peptide1\nPEPMCIDEPR"),
                                                              true, IdentityPath.ROOT, out path);

            // One of the prolines should have caused an extra transition
            Assert.AreEqual(4, docPeptide.PeptideTransitionCount);
            Assert.IsTrue(docPeptide.PeptideTransitions.Contains(nodeTran => nodeTran.Transition.Ordinal == 8));

            const string formula          = "H2O2"; // This was H2O, but that falls below mz=10 at z > 1
            const string hydrogenPeroxide = "Hydrogen Perxoide";
            var          reporterIons     = new[] { new MeasuredIon(hydrogenPeroxide, formula, null, null, Adduct.SINGLY_PROTONATED), new MeasuredIon(hydrogenPeroxide, formula, null, null, Adduct.DOUBLY_PROTONATED), new MeasuredIon(hydrogenPeroxide, formula, null, null, Adduct.TRIPLY_PROTONATED), MeasuredIonList.NTERM_PROLINE };
            SrmDocument  docReporterIon   = docPeptide.ChangeSettings(docPeptide.Settings.ChangeTransitionFilter(filter =>
                                                                                                                 filter.ChangeMeasuredIons(reporterIons)));

            AssertEx.IsDocumentTransitionCount(docReporterIon, 7);

            //Check With Monoisotopic
            var mass = BioMassCalc.MONOISOTOPIC.CalculateMassFromFormula(formula);

            for (int i = 0; i < 3; i++)
            {
                TransitionDocNode tranNode = docReporterIon.MoleculeTransitions.ElementAt(i);
                Transition        tran     = tranNode.Transition;
                Assert.AreEqual(reporterIons[i].SettingsCustomIon, tran.CustomIon);
                Assert.AreEqual(tran.Charge, i + 1);
                Assert.AreEqual(BioMassCalc.CalculateIonMz(mass, tran.Adduct), tranNode.Mz, BioMassCalc.MassElectron / 100);
            }

            //Check with Average
            TransitionPrediction predSettings =
                docReporterIon.Settings.TransitionSettings.Prediction.ChangeFragmentMassType(MassType.Average);
            TransitionSettings tranSettings    = docReporterIon.Settings.TransitionSettings.ChangePrediction(predSettings);
            SrmSettings        srmSettings     = docReporterIon.Settings.ChangeTransitionSettings(tranSettings);
            SrmDocument        averageDocument = docReporterIon.ChangeSettings(srmSettings);

            mass = BioMassCalc.AVERAGE.CalculateMassFromFormula(formula);
            for (int i = 0; i < 3; i++)
            {
                TransitionDocNode tranNode = averageDocument.MoleculeTransitions.ElementAt(i);
                Transition        tran     = tranNode.Transition;
                Assert.AreEqual(reporterIons[i].SettingsCustomIon, tran.CustomIon);
                Assert.AreEqual(tran.Charge, i + 1);
                Assert.AreEqual(BioMassCalc.CalculateIonMz(mass, tran.Adduct), tranNode.Mz, BioMassCalc.MassElectron / 100);
            }

            //Make sure the rest of the transitions aren't reporter ions
            for (int i = 3; i < 7; i++)
            {
                Transition tran = docReporterIon.MoleculeTransitions.ElementAt(i).Transition;
                Assert.AreNotEqual(tran.CustomIon, reporterIons);
            }
            var         optionalIon = new MeasuredIon(hydrogenPeroxide, formula, null, null, Adduct.SINGLY_PROTONATED, true);
            SrmDocument optionalDoc = docPeptide.ChangeSettings(docPeptide.Settings.ChangeTransitionFilter(filter =>
                                                                                                           filter.ChangeMeasuredIons(new[] { optionalIon })));

            Assert.AreEqual(3, optionalDoc.PeptideTransitionCount);
            optionalDoc = optionalDoc.ChangeSettings(optionalDoc.Settings.ChangeTransitionFilter(filter =>
                                                                                                 filter.ChangeMeasuredIons(new[] { optionalIon.ChangeIsOptional(false) })));
            AssertEx.IsDocumentTransitionCount(optionalDoc, 4);
            Assert.AreEqual(optionalIon.ChangeIsOptional(false).SettingsCustomIon,
                            optionalDoc.MoleculeTransitions.ElementAt(0).Transition.CustomIon);
            optionalDoc =
                optionalDoc.ChangeSettings(
                    optionalDoc.Settings.ChangeTransitionFilter(
                        filter => filter.ChangeMeasuredIons(new[] { optionalIon.ChangeIsOptional(true) })));


            TransitionGroupDocNode nodeGroup = optionalDoc.MoleculeTransitionGroups.ElementAt(0);
            var filteredNodes =
                nodeGroup.GetPrecursorChoices(optionalDoc.Settings,
                                              optionalDoc.Molecules.ElementAt(0).ExplicitMods, true)
                .Cast <TransitionDocNode>()
                .Where(node => Equals(node.Transition.CustomIon, optionalIon.SettingsCustomIon));

            var unfilteredNodes =
                nodeGroup.GetPrecursorChoices(optionalDoc.Settings,
                                              optionalDoc.Molecules.ElementAt(0).ExplicitMods, false)
                .Cast <TransitionDocNode>()
                .Where(node => Equals(node.Transition.CustomIon, optionalIon.SettingsCustomIon));

            Assert.AreEqual(0, filteredNodes.Count());
            Assert.AreEqual(1, unfilteredNodes.Count());
        }