public Pre372CustomIonTransitionGroupHandler(XmlReader readerIn, double settingsMzMatchToler) { ReadAhead = new XmlReadAhead(readerIn); // Read the current element and its children // Put the contents of the readahead buffer into parseable XML form, return a reader for that var reader = ReadAhead.CreateXmlReader(); // Advance the reader to the start of the precursors list, if any Assume.IsTrue(reader.IsStartElement(DocumentSerializer.EL.molecule)); while (!reader.IsStartElement(DocumentSerializer.EL.precursor) && !reader.EOF) { reader.Read(); } // Gather info on all declared precursors _precursorRawDetails = new List <PrecursorRawDetails>(); while (reader.IsStartElement(DocumentSerializer.EL.precursor)) { var details = new PrecursorRawDetails { _formulaUnlabeled = string.Empty, _labels = null, _nominalAdduct = Adduct.EMPTY, _proposedAdduct = Adduct.EMPTY, _declaredCharge = reader.GetIntAttribute(DocumentSerializer.ATTR.charge), _declaredMz = reader.GetDoubleAttribute(DocumentSerializer.ATTR.precursor_mz), _declaredHeavy = !IsotopeLabelType.LIGHT_NAME.Equals(reader.GetAttribute(DocumentSerializer.ATTR.isotope_label) ?? IsotopeLabelType.LIGHT_NAME) }; var formula = reader.GetAttribute(DocumentSerializer.ATTR.ion_formula); if (formula != null) { details._formulaUnlabeled = formula.Trim(); // We've seen tailing spaces in the wild string precursorFormula; Adduct precursorAdduct; Molecule precursorMol; if (IonInfo.IsFormulaWithAdduct(formula, out precursorMol, out precursorAdduct, out precursorFormula)) { details._formulaUnlabeled = precursorFormula; details._nominalAdduct = precursorAdduct; } else { details._labels = BioMassCalc.MONOISOTOPIC.FindIsotopeLabelsInFormula(details._formulaUnlabeled); details._formulaUnlabeled = BioMassCalc.MONOISOTOPIC.StripLabelsFromFormula(details._formulaUnlabeled); } } _precursorRawDetails.Add(details); reader.ReadToNextSibling(DocumentSerializer.EL.precursor); } // We want to use the recorded mz values as nearly as possible // Inspect the XML to find the expected precision, compare that with current settings MzToler = ReadAhead.ElementPrecision(DocumentSerializer.EL.precursor_mz); if (MzToler < 1.0) { MzToler *= 5; } MzToler = Math.Min(MzToler, settingsMzMatchToler); }
private void TestException(string formula, string adductText) { AssertEx.ThrowsException <InvalidOperationException>(() => { var adduct = Adduct.FromStringAssumeProtonated(adductText); IonInfo.ApplyAdductToFormula(formula, adduct); }); }
private void TestPentaneAdduct(string adductText, string expectedFormula, int expectedCharge, HashSet <string> coverage) { var adduct = Adduct.FromStringAssumeProtonated(adductText); var actual = IonInfo.ApplyAdductToFormula(PENTANE, adduct).ToString(); Assert.AreEqual(expectedFormula, actual, "unexpected formula for adduct " + adduct); Assert.AreEqual(expectedCharge, adduct.AdductCharge, "unexpected charge for adduct " + adduct); coverage.Add(adduct.AsFormula()); }
protected virtual void ReadAttributes(XmlReader reader, out Adduct embeddedAdduct) { var formula = reader.GetAttribute(ATTR.formula); if (!string.IsNullOrEmpty(formula)) { formula = BioMassCalc.AddH(formula); // Update this old style formula to current by adding the hydrogen we formerly left out due to assuming protonation } else { var text = reader.GetAttribute(ATTR.ion_formula) ?? reader.GetAttribute(ATTR.neutral_formula); if (text != null) { text = text.Trim(); // We've seen some trailing spaces in the wild } formula = text; } string neutralFormula; Molecule mol; // We commonly see the adduct inline with the neutral formula ("C12H5[M+Na]"), so be ready to preserve that if (IonInfo.IsFormulaWithAdduct(formula, out mol, out embeddedAdduct, out neutralFormula)) { formula = neutralFormula; } else { embeddedAdduct = Adduct.EMPTY; } if (string.IsNullOrEmpty(formula)) { AverageMass = ReadAverageMass(reader); MonoisotopicMass = ReadMonoisotopicMass(reader); } Formula = formula; Name = reader.GetAttribute(ATTR.custom_ion_name); if (string.IsNullOrEmpty(Name)) { Name = reader.GetAttribute(ATTR.name) ?? string.Empty; } AccessionNumbers = MoleculeAccessionNumbers.FromSerializableString(reader.GetAttribute(ATTR.id)); Validate(); }
public CustomIon(string formula, Adduct adduct, TypedMass monoisotopicMass, TypedMass averageMass, string name) : base(formula, monoisotopicMass, averageMass, name) { if (adduct.IsEmpty) { var ionInfo = new IonInfo(NeutralFormula, adduct); // Analyzes the formula to see if it's something like "CH12[M+Na]" if (!Equals(NeutralFormula, ionInfo.NeutralFormula)) { Formula = ionInfo.NeutralFormula; } Adduct = Adduct.FromStringAssumeProtonated(ionInfo.AdductText); } else { Adduct = adduct; } }
public static FragmentedMolecule GetFragmentedMolecule(SrmSettings settings, PeptideDocNode peptideDocNode, TransitionGroupDocNode transitionGroupDocNode, TransitionDocNode transitionDocNode) { FragmentedMolecule fragmentedMolecule = EMPTY .ChangePrecursorMassShift(0, settings.TransitionSettings.Prediction.PrecursorMassType) .ChangeFragmentMassShift(0, settings.TransitionSettings.Prediction.FragmentMassType); if (peptideDocNode == null) { return(fragmentedMolecule); } var labelType = transitionGroupDocNode == null ? IsotopeLabelType.light : transitionGroupDocNode.TransitionGroup.LabelType; if (peptideDocNode.IsProteomic) { fragmentedMolecule = fragmentedMolecule.ChangeModifiedSequence( ModifiedSequence.GetModifiedSequence(settings, peptideDocNode, labelType)); if (transitionGroupDocNode != null) { fragmentedMolecule = fragmentedMolecule .ChangePrecursorCharge(transitionGroupDocNode.PrecursorCharge); } if (transitionDocNode == null || transitionDocNode.IsMs1) { return(fragmentedMolecule); } var transition = transitionDocNode.Transition; fragmentedMolecule = fragmentedMolecule .ChangeFragmentIon(transition.IonType, transition.Ordinal) .ChangeFragmentCharge(transition.Charge); var transitionLosses = transitionDocNode.Losses; if (transitionLosses != null) { var fragmentLosses = transitionLosses.Losses.Select(transitionLoss => transitionLoss.Loss); fragmentedMolecule = fragmentedMolecule.ChangeFragmentLosses(fragmentLosses); } return(fragmentedMolecule); } if (transitionGroupDocNode == null) { return(fragmentedMolecule .ChangePrecursorFormula( Molecule.Parse(peptideDocNode.CustomMolecule.Formula ?? string.Empty))); } var customMolecule = transitionGroupDocNode.CustomMolecule; fragmentedMolecule = fragmentedMolecule.ChangePrecursorCharge(transitionGroupDocNode.TransitionGroup .PrecursorCharge); if (customMolecule.Formula != null) { var ionInfo = new IonInfo(customMolecule.Formula, transitionGroupDocNode.PrecursorAdduct); fragmentedMolecule = fragmentedMolecule .ChangePrecursorFormula(Molecule.Parse(ionInfo.FormulaWithAdductApplied)); } else { fragmentedMolecule = fragmentedMolecule.ChangePrecursorMassShift( transitionGroupDocNode.PrecursorAdduct.MassFromMz( transitionGroupDocNode.PrecursorMz, transitionGroupDocNode.PrecursorMzMassType), transitionGroupDocNode.PrecursorMzMassType); } if (transitionDocNode == null || transitionDocNode.IsMs1) { return(fragmentedMolecule); } var customIon = transitionDocNode.Transition.CustomIon; if (customIon.Formula != null) { fragmentedMolecule = fragmentedMolecule.ChangeFragmentFormula( Molecule.Parse(customIon.FormulaWithAdductApplied)); } else { fragmentedMolecule = fragmentedMolecule.ChangeFragmentMassShift( transitionDocNode.Transition.Adduct.MassFromMz( transitionDocNode.Mz, transitionDocNode.MzMassType), transitionDocNode.MzMassType); } fragmentedMolecule = fragmentedMolecule .ChangeFragmentCharge(transitionDocNode.Transition.Charge); return(fragmentedMolecule); }
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); } } } }
private void UpdateAverageAndMonoTextsForFormula() { bool valid; try { var formula = Formula; // Get current formula and adduct var userinput = textFormula.Text.Trim(); if (_editMode == EditMode.adduct_only) { if (!string.IsNullOrEmpty(userinput) && !userinput.StartsWith(@"[")) { // Assume they're trying to type an adduct userinput = @"[" + userinput + @"]"; } if (string.IsNullOrEmpty(NeutralFormula)) { formula = null; // Parent molecule was described as mass only } else { formula = NeutralFormula + userinput; // Try to apply this new adduct to parent molecule } } else { formula = userinput; } string neutralFormula; Molecule ion; Adduct adduct; if (!IonInfo.IsFormulaWithAdduct(formula, out ion, out adduct, out neutralFormula)) { neutralFormula = formula; if (!Adduct.TryParse(userinput, out adduct)) { adduct = Adduct.EMPTY; } } if (_editMode != EditMode.adduct_only) { NeutralFormula = neutralFormula; } if (_editMode != EditMode.formula_only) { Adduct = adduct; } // Update mass/mz displays if (string.IsNullOrEmpty(neutralFormula)) { if (!adduct.IsEmpty) { // No formula, but adduct changed Adduct = adduct; // ReSharper disable once PossibleNullReferenceException GetTextFromMass(_neutralMonoMass, MassType.Monoisotopic); // Just to see if it throws or not GetTextFromMass(_neutralAverageMass, MassType.Average); // Just to see if it throws or not } } else { // Is there an isotopic label we should apply to get the mass? if (IsotopeLabelsForMassCalc != null && (Adduct.IsEmpty || !Adduct.HasIsotopeLabels)) // If adduct declares an isotope, that takes precedence { neutralFormula = IsotopeLabelsForMassCalc.Aggregate(neutralFormula, (current, kvp) => current.Replace(kvp.Key, kvp.Value)); } var monoMass = SequenceMassCalc.FormulaMass(BioMassCalc.MONOISOTOPIC, neutralFormula, SequenceMassCalc.MassPrecision); var averageMass = SequenceMassCalc.FormulaMass(BioMassCalc.AVERAGE, neutralFormula, SequenceMassCalc.MassPrecision); GetTextFromMass(monoMass, MassType.Monoisotopic); // Just to see if it throws or not GetTextFromMass(averageMass, MassType.Average); // Just to see if it throws or not MonoMass = monoMass; AverageMass = averageMass; } valid = true; // If we got here, formula parsed OK, or adduct did textFormula.ForeColor = Color.Black; if (_editMode == EditMode.adduct_only) { textFormula.Text = userinput; // Enforce proper adduct formatting if (adduct.IsEmpty) { valid = false; // Adduct did not parse } } else if (_editMode == EditMode.formula_only) { valid &= adduct.IsEmpty; // Should not have anything going on with adduct here } } catch (InvalidOperationException) { valid = false; } catch (ArgumentException) { valid = false; } if (valid) { textFormula.ForeColor = Color.Black; } else { textFormula.ForeColor = Color.Red; textMono.Text = textAverage.Text = string.Empty; } // Allow direct editing of masses if direct editing of formula is allowed, but formula is empty MassEnabled = _editMode != EditMode.adduct_only && string.IsNullOrEmpty(_neutralFormula); }