private XmlReader ConsiderMassShiftAdducts(ref Peptide peptide)
        {
            var commonFormula = ProposedMolecule.Formula;
            var molMass       = ProposedMolecule.MonoisotopicMass;

            foreach (var d in _precursorRawDetails.Where(d => Adduct.IsNullOrEmpty(d._proposedAdduct)))
            {
                if (!Adduct.IsNullOrEmpty(d._nominalAdduct) && Math.Abs(d._declaredMz - d._nominalAdduct.MzFromNeutralMass(molMass)) <= MzToler)
                {
                    d._proposedAdduct = d._nominalAdduct;
                }
            }

            // Final attempt at assigning adducts in the event that no adjustment to neutral molecule has worked out
            foreach (var d in _precursorRawDetails.Where(d => Adduct.IsNullOrEmpty(d._proposedAdduct)))
            {
                // So far we haven't hit on a common molecules + adducts scenario that explains the given mz
                // We're forced to add weird mass shifts to adducts
                for (var retry = 0; retry < 4; retry++)
                {
                    Adduct adduct;
                    switch (retry)
                    {
                    case 0:     // Try [M+H] style
                        adduct = Adduct.ProtonatedFromFormulaDiff(d._formulaUnlabeled, commonFormula, d._declaredCharge)
                                 .ChangeIsotopeLabels(d._labels);
                        break;

                    case 1:     // Try [M+] style
                        adduct = Adduct.FromFormulaDiff(d._formulaUnlabeled, commonFormula, d._declaredCharge)
                                 .ChangeIsotopeLabels(d._labels);
                        break;

                    default:
                        adduct = retry == 2
                                ? Adduct.ProtonatedFromFormulaDiff(d._formulaUnlabeled, commonFormula,
                                                                   d._declaredCharge)                            // Try [M1.2345+H] style
                                : Adduct.FromFormulaDiff(d._formulaUnlabeled, commonFormula, d._declaredCharge); // Try [M1.2345+] style
                        var ionMass         = adduct.MassFromMz(d._declaredMz, MassType.Monoisotopic);
                        var unexplainedMass = ionMass - molMass;
                        adduct = adduct.ChangeIsotopeLabels(unexplainedMass, _mzDecimalPlaces);
                        break;
                    }
                    if (Math.Abs(d._declaredMz - adduct.MzFromNeutralMass(molMass)) <= MzToler)
                    {
                        d._proposedAdduct = adduct;
                        break;
                    }
                }
            }
            Assume.IsTrue(_precursorRawDetails.TrueForAll(d => !Adduct.IsNullOrEmpty(d._proposedAdduct)),
                          "Unable to to deduce adducts and common molecule for " + peptide); // Not L10N

            // We found a satisfactory set of adducts and neutral molecule, update the "peptide" and
            // modify the readahead buffer with the new adduct info
            return(UpdatePeptideAndInsertAdductsInXML(ref peptide, _precursorRawDetails.Select(d => d._proposedAdduct)));
        }
        private bool DeriveAdductsForCommonFormula(Peptide peptide)
        {
            // Try to come up with a set of adducts to common formula that explain the declared mz values
            var success = false;

            if (_precursorRawDetails.TrueForAll(d => Adduct.IsNullOrEmpty(d._nominalAdduct)))
            {
                // No explicit adducts, just charges.
                // Start with the most common scenario, which is that the user meant (de)protonation
                // See if we can arrive at a common formula ( by adding or removing H) that works with all charges as (de)protonations
                // N.B. the parent molecule may well be completely unrelated to the children, as users were allowed to enter anything they wanted
                var commonFormula          = ProposedMolecule.Formula;
                var precursorsWithFormulas = _precursorRawDetails.Where(d => !string.IsNullOrEmpty(d._formulaUnlabeled)).ToList();
                foreach (var detail in precursorsWithFormulas)
                {
                    var revisedCommonFormula = Molecule.AdjustElementCount(commonFormula, BioMassCalc.H, -detail._declaredCharge);
                    var adjustedMolecule     = new CustomMolecule(revisedCommonFormula, peptide.CustomMolecule.Name);
                    var mass = adjustedMolecule.MonoisotopicMass;
                    if (precursorsWithFormulas.TrueForAll(d =>
                    {
                        d._proposedAdduct = Adduct.ProtonatedFromFormulaDiff(d._formulaUnlabeled, revisedCommonFormula, d._declaredCharge)
                                            .ChangeIsotopeLabels(d._labels);
                        return(Math.Abs(d._declaredMz - d._proposedAdduct.MzFromNeutralMass(mass)) <= MzToler);
                    }))
                    {
                        ProposedMolecule = adjustedMolecule;
                        success          = true;
                        break;
                    }
                }
                success &= _precursorRawDetails.All(d => !Adduct.IsNullOrEmpty(d._proposedAdduct));
                if (!success)
                {
                    foreach (var d in _precursorRawDetails)
                    {
                        d._proposedAdduct = Adduct.EMPTY;
                    }
                }
            }
            return(success);
        }
Ejemplo n.º 3
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);
                    }
                }
            }
        }