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()); }
public Adduct GetProductAdduct(Adduct precursorAdduct) { var totalCharge = TotalCharge; if (totalCharge == 0) { return(precursorAdduct); } var newCharge = precursorAdduct.AdductCharge - totalCharge; if (Math.Sign(newCharge) != Math.Sign(precursorAdduct.AdductCharge)) { return(null); } return(precursorAdduct.ChangeCharge(newCharge)); }
private static void CheckRefSpectra(IList <DbRefSpectra> spectra, string name, string formula, string precursorAdduct, double precursorMz, ushort numPeaks) { name = RefinementSettings.TestingConvertedFromProteomicPeptideNameDecorator + name; for (var i = 0; i < spectra.Count; i++) { var spectrum = spectra[i]; if (spectrum.MoleculeName.Equals(name) && spectrum.ChemicalFormula.Equals(formula) && spectrum.PrecursorCharge.Equals(Adduct.FromStringAssumeProtonated(precursorAdduct).AdductCharge) && spectrum.PrecursorAdduct.Equals(precursorAdduct) && Math.Abs(spectrum.PrecursorMZ - precursorMz) < 0.001 && spectrum.NumPeaks.Equals(numPeaks)) { spectra.RemoveAt(i); return; } } Assume.Fail(string.Format("{0}, {1}, precursor charge {2}, precursor m/z {3}, with {4} peaks not found", name, formula, precursorAdduct, precursorMz, numPeaks)); }
private static void VerifySharedDocLibraryAnnotations(string shareDocPath) { RunUI(() => SkylineWindow.OpenSharedFile(shareDocPath)); var doc = WaitForDocumentLoaded(); AssertEx.IsDocumentState(doc, null, 1, 4, 4, 4); // int revision, int groups, int peptides, int tranGroups, int transitions SpectrumPeaksInfo spectrum; IsotopeLabelType label; // ReSharper disable PossibleNullReferenceException doc.Settings.TryLoadSpectrum(doc.Molecules.FirstOrDefault().Target, Adduct.FromStringAssumeChargeOnly("M+NH4"), null, out label, out spectrum); Assume.IsTrue(spectrum.Annotations.Count() == 1); var spectrumPeakAnnotations = (spectrum.Annotations.FirstOrDefault() ?? new SpectrumPeakAnnotation[0]).ToArray(); Assume.IsTrue(spectrumPeakAnnotations.Length == 1); Assume.IsTrue(spectrumPeakAnnotations.FirstOrDefault().Ion.Name == "GP"); // ReSharper restore PossibleNullReferenceException }
public static Adduct GetChargeFromIndicator(string text, int min, int max, out int foundAt) { foundAt = -1; if (!MayHaveChargeIndicator(text)) { return(Adduct.EMPTY); } // Handle runs of charge characters no matter how long, because users guess this should work foundAt = FindChargeSymbolRepeatStart('+', text, min, max); if (foundAt != -1) { return(Adduct.FromChargeProtonated(text.Length - foundAt)); } foundAt = FindChargeSymbolRepeatStart('-', text, min, max); if (foundAt != -1) { return(Adduct.FromChargeProtonated(foundAt - text.Length)); } Adduct adduct; for (int i = max; i >= min; i--) { adduct = GetChargeFromIndicator(text, i, out foundAt); if (!adduct.IsEmpty) { return(adduct); } adduct = GetChargeFromIndicator(text, -i, out foundAt); if (!adduct.IsEmpty) { return(adduct); } } foundAt = FindAdductDescription(text, out adduct); if (foundAt != -1) { return(adduct); } return(Adduct.EMPTY); }
public Transition(TransitionGroup group, IonType type, int?offset, int?massIndex, Adduct adduct, int?decoyMassShift, CustomMolecule customMolecule = null) { _group = group; IonType = type; CleavageOffset = offset ?? 0; MassIndex = massIndex ?? 0; Adduct = adduct; DecoyMassShift = decoyMassShift; // Small molecule precursor transition should have same custom molecule as parent if (IsPrecursor(type) && group.IsCustomIon) { CustomIon = new CustomIon(group.CustomMolecule, adduct); } else if (customMolecule is CustomIon) { // As with reporter ions CustomIon = (CustomIon)customMolecule; Assume.IsTrue(Equals(adduct.AdductCharge, CustomIon.Adduct.AdductCharge)); Adduct = CustomIon.Adduct; // Ion mass is part of formula, so use charge only adduct } else if (customMolecule != null) { CustomIon = new CustomIon(customMolecule, adduct); } // Derived values if (!IsCustom(type, group)) { Peptide peptide = group.Peptide; Ordinal = OffsetToOrdinal(type, (int)offset, peptide.Length); AA = (IsNTerminal() ? peptide.Sequence[(int)offset] : peptide.Sequence[(int)offset + 1]); } else { // caller may have passed in offset = group.Peptide.Length - 1, which for custom ions gives -1 CleavageOffset = 0; } Validate(); }
private static int FindChargeIndicatorPos(string line, int min, int max, CultureInfo cultureInfo) { for (int i = max; i >= min; i--) { // Handle negative charges int pos = FindChargeIndicatorPos(line, GetChargeIndicator(Adduct.FromChargeProtonated(-i), cultureInfo)); if (pos != -1) { return(pos); } // Handle positive charges pos = FindChargeIndicatorPos(line, GetChargeIndicator(Adduct.FromChargeProtonated(i), cultureInfo)); if (pos != -1) { return(pos); } } return(-1); }
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); }
/// <summary> /// Calculates the matching charge within a tolerance for a mass, assuming (de)protonation. /// </summary> /// <param name="mass">The mass to calculate charge for (actually massH if !IsCustomIon)</param> /// <param name="mz">The desired m/z value the charge should produce</param> /// <param name="tolerance">How far off the actual m/z is allowed to be</param> /// <param name="isCustomIon">Is this a custom ion formula?</param> /// <param name="minCharge">Minimum charge to consider</param> /// <param name="maxCharge">Maximum charge to consider</param> /// <param name="massShifts">Possible mass shifts that may have been applied to decoys</param> /// <param name="massShiftType"></param> /// <param name="massShift">Mass shift required to to achieve this charge state or zero</param> /// <param name="nearestCharge">closest matching charge, useful when return value is null</param> /// <returns>A matching charge or null, in which case the closest non-matching charge can be found in the nearestCharge value.</returns> public static Adduct CalcCharge(TypedMass mass, double mz, double tolerance, bool isCustomIon, int minCharge, int maxCharge, ICollection <int> massShifts, MassShiftType massShiftType, out int massShift, out int nearestCharge) { Assume.IsTrue(minCharge <= maxCharge); massShift = 0; nearestCharge = 0; double nearestDelta = double.MaxValue; for (int i = minCharge; i <= maxCharge; i++) { if (i != 0) // Avoid z=0 if we're entertaining negative charge states { double delta = mz - (isCustomIon ? Adduct.FromChargeProtonated(i).MzFromNeutralMass(mass) : SequenceMassCalc.GetMZ(mass, 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; } var result = i; nearestCharge = i; return(Adduct.FromChargeProtonated(result)); } if (deltaAbs < nearestDelta) { nearestDelta = deltaAbs; nearestCharge = i; } } } Debug.Assert(nearestCharge != 0); // Could only happen if min > max return(Adduct.EMPTY); }
private void LoadPeptides(IEnumerable <DbIonMobilityPeptide> peptides) { var dictLibrary = new Dictionary <LibKey, DbIonMobilityPeptide>(); foreach (var pep in peptides) { var dict = dictLibrary; try { DbIonMobilityPeptide ignored; var adduct = pep.GetPrecursorAdduct(); if (adduct.IsEmpty) { // Older formats didn't consider charge to be a factor is CCS, so just fake up M+H, M+2H and M+3H for (int z = 1; z <= 3; z++) { var newPep = new DbIonMobilityPeptide(pep.GetNormalizedModifiedSequence(), Adduct.FromChargeProtonated(z), pep.CollisionalCrossSection, pep.HighEnergyDriftTimeOffsetMsec); var key = newPep.GetLibKey(); if (!dict.TryGetValue(key, out ignored)) { dict.Add(key, newPep); } } } else { var key = pep.GetLibKey(); if (!dict.TryGetValue(key, out ignored)) { dict.Add(key, pep); } } } catch (ArgumentException) { } } DictLibrary = dictLibrary; }
public void WriteXml(XmlWriter writer, Adduct adduct) { if (adduct.IsEmpty) { writer.WriteAttributeIfString(ATTR.neutral_formula, Formula); } else { writer.WriteAttributeIfString(ATTR.ion_formula, (Formula ?? string.Empty) + (adduct.IsProteomic ? string.Empty : adduct.ToString())); } Assume.IsFalse(AverageMass.IsMassH()); // We're going to read these as neutral masses Assume.IsFalse(MonoisotopicMass.IsMassH()); writer.WriteAttributeNullable(ATTR.neutral_mass_average, AverageMass); writer.WriteAttributeNullable(ATTR.neutral_mass_monoisotopic, MonoisotopicMass); if (!string.IsNullOrEmpty(Name)) { writer.WriteAttribute(ATTR.custom_ion_name, Name); } writer.WriteAttributeIfString(ATTR.id, AccessionNumbers.ToSerializableString()); }
// Helper function for PrecursorIonFormula and PrecursorNeutralFormula private void GetPrecursorFormulaAndAdduct(out Adduct adduct, out string formula) { if (IsSmallMolecule()) { formula = (DocNode.CustomMolecule.Formula ?? string.Empty); adduct = DocNode.PrecursorAdduct; } else { PeptideDocNode parent = DataSchema.Document.FindNode(IdentityPath.Parent) as PeptideDocNode; if (parent == null) { adduct = Util.Adduct.EMPTY; formula = String.Empty; return; } var molecule = RefinementSettings.ConvertToSmallMolecule( RefinementSettings.ConvertToSmallMoleculesMode.formulas, SrmDocument, parent, out adduct, DocNode.TransitionGroup.PrecursorAdduct.AdductCharge, DocNode.TransitionGroup.LabelType); formula = molecule.Formula ?? string.Empty; } }
public MockTranPeakData(double[] data, IonType ionType = IonType.a, IsotopeLabelType labelType = null, int?charge = null, double?massError = null, double libIntensity = 0, double?isotopeProportion = null) { if (labelType == null) { labelType = IsotopeLabelType.light; } PeakData = new MockPeakData(data, massError) as TPeak; var peptide = new Peptide(null, "AVVAVVA", null, null, 0); charge = charge ?? 2; var tranGroup = new TransitionGroup(peptide, Adduct.FromChargeProtonated(charge), labelType); int offset = ionType == IonType.precursor ? 6 : 0; var isotopeInfo = isotopeProportion == null ? null : new TransitionIsotopeDistInfo(1, (float)isotopeProportion); NodeTran = new TransitionDocNode(new Transition(tranGroup, ionType, offset, 0, Adduct.FromChargeProtonated(charge), null), null, TypedMass.ZERO_MONO_MASSH, new TransitionDocNode.TransitionQuantInfo(isotopeInfo, new TransitionLibInfo(1, (float)libIntensity), true)); }
private double?_monoMass; // Our internal value for mass, regardless of whether displaying mass or mz /// <summary> /// Reusable control for dealing with chemical formulas and their masses /// </summary> /// <param name="isProteomic">if true, don't offer Cl, Br, or heavy P or heavy S in elements popup</param> /// <param name="labelFormulaText">Label text for the formula textedit control</param> /// <param name="labelAverageText">Label text for the average mass or m/z textedit control</param> /// <param name="labelMonoText">Label text for the monoisotopic mass or m/z textedit control</param> /// <param name="adduct">If non-null, treat the average and monoisotopic textedits as describing m/z instead of mass</param> /// <param name="mode">Controls editing of the formula and/or adduct edit</param> /// <param name="suggestOnlyAdductsWithMass">If presenting an adduct dropdown menu, do we include things like "[M+]"?</param> public FormulaBox(bool isProteomic, string labelFormulaText, string labelAverageText, string labelMonoText, Adduct adduct, EditMode mode = EditMode.formula_only, bool suggestOnlyAdductsWithMass = true) { InitializeComponent(); if (isProteomic) { // Don't offer exotic atoms or isotopes p32ToolStripMenuItem.Visible = s33ToolStripMenuItem.Visible = s34ToolStripMenuItem.Visible = h3ToolStripMenuItem.Visible = clToolStripMenuItem.Visible = cl37ToolStripMenuItem.Visible = brToolStripMenuItem.Visible = br81ToolStripMenuItem.Visible = false; } _adduct = adduct; _editMode = mode; switch (mode) { case EditMode.adduct_only: case EditMode.formula_and_adduct: TransitionSettingsUI.AppendAdductMenus(contextFormula, suggestOnlyAdductsWithMass, adductStripMenuItem_Click); break; } toolTip1.SetToolTip(textFormula, _editMode == EditMode.adduct_only ? AdductHelpText : FormulaHelpText); // Explain how formulas work, and ion formula adducts if charge.HasValue labelFormula.Text = labelFormulaText; labelAverage.Text = labelAverageText; labelMono.Text = labelMonoText; Bitmap bm = Resources.PopupBtn; bm.MakeTransparent(Color.Fuchsia); btnFormula.Image = bm; }
private TransitionDocNode TransitionFromPeakAndAnnotations(LibKey key, TransitionGroupDocNode nodeGroup, Adduct fragmentCharge, SpectrumPeaksInfo.MI peak, int?rank) { var charge = fragmentCharge; var monoisotopicMass = charge.MassFromMz(peak.Mz, MassType.Monoisotopic); var averageMass = charge.MassFromMz(peak.Mz, MassType.Average); // Caution here - library peak (observed) mz may not exactly match (theoretical) mz of the annotation // In the case of multiple annotations, produce single transition for display in library explorer var annotations = peak.GetAnnotationsEnumerator().ToArray(); var spectrumPeakAnnotationIon = peak.AnnotationsAggregateDescriptionIon; var molecule = spectrumPeakAnnotationIon.Adduct.IsEmpty ? new CustomMolecule(monoisotopicMass, averageMass) : spectrumPeakAnnotationIon; var note = (annotations.Length > 1) ? TextUtil.LineSeparate(annotations.Select(a => a.ToString())) : null; var noteIfAnnotationMzDisagrees = NoteIfAnnotationMzDisagrees(key, peak); if (noteIfAnnotationMzDisagrees != null) { if (note == null) { note = noteIfAnnotationMzDisagrees; } else { note = TextUtil.LineSeparate(note, noteIfAnnotationMzDisagrees); } } var transition = new Transition(nodeGroup.TransitionGroup, spectrumPeakAnnotationIon.Adduct.IsEmpty ? charge : spectrumPeakAnnotationIon.Adduct, 0, molecule); return(new TransitionDocNode(transition, Annotations.EMPTY.ChangeNote(note), null, monoisotopicMass, rank.HasValue ? new TransitionDocNode.TransitionQuantInfo(null, new TransitionLibInfo(rank.Value, peak.Intensity), true) : TransitionDocNode.TransitionQuantInfo.DEFAULT, ExplicitTransitionValues.EMPTY, null)); }
public static Adduct GetChargeFromIndicator(string text, int min, int max, out int foundAt) { foundAt = -1; if (!MayHaveChargeIndicator(text)) { return(Adduct.EMPTY); } Adduct adduct; for (int i = max; i >= min; i--) { // Handle negative charges adduct = Adduct.FromChargeProtonated(-i); var chargeIndicator = GetChargeIndicator(adduct); if (text.EndsWith(chargeIndicator)) { foundAt = text.Length - chargeIndicator.Length; return(adduct); } adduct = Adduct.FromChargeProtonated(i); chargeIndicator = GetChargeIndicator(adduct); if (text.EndsWith(chargeIndicator)) { foundAt = text.Length - chargeIndicator.Length; return(adduct); } } var adductStart = FindAdductDescription(text, out adduct); if (adductStart >= 0) { foundAt = adductStart; return(adduct); } return(Adduct.EMPTY); }
protected override void DoTest() { var testFilesDir = TestFilesDir; // Verify our use of explict RT where multiple nodes and multiple chromatograms all have same Q1>Q3 // This data set has three chromatograms with Q1=150 Q3=150 (one in negative ion mode), and // three transition nodes with that Q1>Q3 but different RTs (one with neg charge) // Expected alignment: // function 65/ index 242 : RT 6.95 glutamate // function 66/ index 243 : RT 7.95 glutamine (peak found at 8.1, but declared explicit RT window is 7.95 +/- .1 so no match) // function 197/ index 117 (neg ion mode): RT 6.4 alpha_ketogluterate DoSubTest(testFilesDir, "glutes.sky", new[] { 6.95, 0, 6.4 }, new[] { "090215_033" }, null, new[] { Adduct.M_PLUS_H, Adduct.M_PLUS_H, Adduct.M_MINUS_H }); // Verify our handling of two Q1>Q3 transitions with no RT overlap - formerly we just ignored one or the other though both are needed // As in https://skyline.gs.washington.edu/labkey/announcements/home/support/thread.view?entityId=924e3c51-7c00-1033-9ff1-da202582a252&_anchor=24723 DoSubTest(testFilesDir, "lysine.sky", new[] { 13.8, 13.8, 13.8, 13.8 }, new[] { "ESvKprosp_20151120_035", "ESvKprosp_20151120_036" }, .5, new[] { Adduct.M_PLUS_H, Adduct.FromStringAssumeChargeOnly("[M6C132N15+H]") }); // Verify our use of explict RT in peak picking where the correct peak isn't the largest // As in https://skyline.gs.washington.edu/labkey/announcements/home/support/thread.view?entityId=273ccc30-8258-1033-9ff1-da202582a252&_anchor=24774 DoSubTest(testFilesDir, "test_b.sky", new[] { 9.1, 9.1, 9.1, 9.1, 9.1, 9.1, 9.1, 9.1 }, new[] { "120315_120", "120315_121", "120315_125", "120315_126" }, null, new[] { Adduct.M_PLUS_H, Adduct.M_PLUS_H }); }
/// <summary> /// Get a mass value from the text string, treating the string as m/z info if we have a charge state /// </summary> private double?GetMassFromText(string text, MassType massType) { try { if (String.IsNullOrEmpty(text)) { return(null); } else { double parsed = double.Parse(text); if (!Adduct.IsEmpty) { // Convert from m/z to mass return(Adduct.MassFromMz(parsed, massType)); } return(parsed); } } catch (Exception) { return(null); } }
public static string GetChargeIndicator(Adduct adduct, CultureInfo cultureInfo) { if (!adduct.IsProteomic && !adduct.IsChargeOnly) { return(adduct.AsFormulaOrSignedInt()); } var charge = adduct.AdductCharge; if (charge >= 0) { const string pluses = "++++"; return(charge <= pluses.Length ? pluses.Substring(0, Math.Min(charge, pluses.Length)) : string.Format(@"{0} +{1}", GetChargeSeparator(cultureInfo), charge)); } else { const string minuses = "--"; charge = -charge; return(charge <= minuses.Length ? minuses.Substring(0, Math.Min(charge, minuses.Length)) : string.Format(@"{0} -{1}", GetChargeSeparator(cultureInfo), charge)); } }
public static string GetChargeIndicator(Adduct adduct) { if (!adduct.IsProteomic && !adduct.IsChargeOnly) { return(adduct.AsFormulaOrSignedInt()); } var charge = adduct.AdductCharge; if (charge >= 0) { const string pluses = "++++"; // Not L10N return(charge <= pluses.Length ? pluses.Substring(0, Math.Min(charge, pluses.Length)) : string.Format("{0} +{1}", LocalizationHelper.CurrentCulture.NumberFormat.NumberGroupSeparator, charge)); // Not L10N } else { const string minuses = "--"; // Not L10N charge = -charge; return(charge <= minuses.Length ? minuses.Substring(0, Math.Min(charge, minuses.Length)) : string.Format("{0} -{1}", LocalizationHelper.CurrentCulture.NumberFormat.NumberGroupSeparator, charge)); // Not L10N } }
public static bool IsFormulaWithAdduct(string formula, out Molecule molecule, out Adduct adduct, out string neutralFormula) { molecule = null; adduct = Adduct.EMPTY; neutralFormula = null; if (string.IsNullOrEmpty(formula)) { return(false); } // Does formula contain an adduct description? If so, pull charge from that. var parts = formula.Split('['); if (parts.Length == 2 && parts[1].Count(c => c == ']') == 1) { neutralFormula = parts[0]; var adductString = formula.Substring(neutralFormula.Length); if (Adduct.TryParse(adductString, out adduct)) { molecule = neutralFormula.Length > 0 ? ApplyAdductToFormula(neutralFormula, adduct) : Molecule.Empty; return(true); } } return(false); }
/// <summary> /// Creates a ComplexFragmentIon representing something which has no amino acids from the parent peptide. /// </summary> public static ComplexFragmentIon NewOrphanFragmentIon(TransitionGroup transitionGroup, ExplicitMods explicitMods, Adduct adduct) { var transition = new Transition(transitionGroup, IonType.precursor, transitionGroup.Peptide.Sequence.Length - 1, 0, adduct); return(new ComplexFragmentIon(transition, null, explicitMods?.Crosslinks, true)); }
private void TestAdductOperators() { // Test some underlying formula handling for fanciful user-supplied values Assert.IsTrue(Molecule.AreEquivalentFormulas("C10H30Si5O5H-CH4", "C9H27O5Si5")); Assert.AreEqual("C7H27O5Si4", BioMassCalc.MONOISOTOPIC.FindFormulaIntersection(new[] { "C8H30Si5O5H-CH4", "C9H27O5Si4", "C9H27O5Si5Na" })); Assert.AreEqual("C7H27O5Si4", BioMassCalc.MONOISOTOPIC.FindFormulaIntersectionUnlabeled(new[] { "C7C'H30Si5O5H-CH4", "C9H27O5Si4", "C9H25H'2O5Si5Na" })); // There is a difference between a proteomic adduct and non proteomic, primarily in how they display Assert.AreEqual(Adduct.FromStringAssumeChargeOnly("M+H"), Adduct.M_PLUS_H); Assert.AreEqual(Adduct.FromStringAssumeProtonatedNonProteomic("1"), Adduct.M_PLUS_H); Assert.AreEqual(Adduct.FromStringAssumeChargeOnly("1"), Adduct.M_PLUS); Assert.AreEqual(Adduct.FromStringAssumeProtonatedNonProteomic("M+H"), Adduct.M_PLUS_H); Assert.AreEqual(Adduct.FromStringAssumeProtonated("1"), Adduct.SINGLY_PROTONATED); Assert.AreEqual(Adduct.FromStringAssumeProtonated("M+H"), Adduct.SINGLY_PROTONATED); Assert.AreEqual(Adduct.FromStringAssumeChargeOnly("M+H").AsFormula(), Adduct.SINGLY_PROTONATED.AsFormula()); // But the underlying chemistry is the same Assert.AreEqual(Adduct.FromStringAssumeProtonated("[M+S]+"), Adduct.FromStringAssumeProtonated("M+S").ChangeCharge(1)); Assert.AreEqual(Adduct.FromStringAssumeProtonated("M(-1.234)+2Na"), Adduct.FromStringAssumeProtonated("M(-1.234)+3Na").ChangeCharge(2)); Assert.AreEqual(Adduct.FromStringAssumeProtonated("M1.234+2Na"), Adduct.FromStringAssumeProtonated("M1.234+3Na").ChangeCharge(2)); Assert.AreEqual(Adduct.FromStringAssumeProtonated("M2Cl37-2Na"), Adduct.FromStringAssumeProtonated("M2Cl37+3Na").ChangeCharge(-2)); Assert.AreEqual(Adduct.FromStringAssumeProtonated("M1.234-2Na"), Adduct.FromStringAssumeProtonated("M1.234+3Na").ChangeCharge(-2)); Assert.AreEqual(Adduct.FromStringAssumeProtonated("M(-1.234)-2Na"), Adduct.FromStringAssumeProtonated("M(-1.234)+3Na").ChangeCharge(-2)); Assert.IsFalse(Adduct.M_PLUS_H.IsProteomic); Assert.IsTrue(Adduct.M_PLUS_H.IsProtonated); Assert.IsTrue(Adduct.SINGLY_PROTONATED.IsProteomic); Assert.IsTrue(Adduct.SINGLY_PROTONATED.IsProtonated); Assert.IsFalse(Adduct.SINGLY_PROTONATED.IsEmpty); Assert.IsFalse(Adduct.EMPTY.IsProteomic); Assert.IsTrue(Adduct.EMPTY.IsEmpty); // Exercise the ability to work with masses and isotope labels Assert.IsTrue(ReferenceEquals(Adduct.SINGLY_PROTONATED, Adduct.SINGLY_PROTONATED.Unlabeled)); var nolabel = Adduct.FromStringAssumeProtonated("M-2Na"); var label = Adduct.FromStringAssumeProtonated("M2Cl37-2Na"); Assert.AreEqual(nolabel, label.Unlabeled); Assert.IsTrue(ReferenceEquals(nolabel, nolabel.Unlabeled)); Assert.IsFalse(nolabel.MassFromMz(300.0, MassType.Monoisotopic).IsHeavy()); Assert.IsFalse(label.MassFromMz(300.0, MassType.Monoisotopic).IsHeavy()); Assert.IsTrue(label.MassFromMz(300.0, MassType.MonoisotopicHeavy).IsHeavy()); Assert.IsTrue(label.MassFromMz(300.0, MassType.Monoisotopic).IsMonoIsotopic()); Assert.IsFalse(nolabel.MassFromMz(300.0, MassType.Average).IsHeavy()); Assert.IsFalse(label.MassFromMz(300.0, MassType.Average).IsHeavy()); Assert.IsTrue(label.MassFromMz(300.0, MassType.AverageHeavy).IsHeavy()); Assert.IsTrue(label.MassFromMz(300.0, MassType.Average).IsAverage()); var massHeavy = label.ApplyToMass(new TypedMass(300, MassType.MonoisotopicHeavy)); // Will not have isotope effect added in mz calc, as it's already heavy var massLight = label.ApplyToMass(new TypedMass(300, MassType.Monoisotopic)); // Will have isotope effect added in mz calc Assert.AreNotEqual(massHeavy, massLight); Assert.AreNotEqual(label.MzFromNeutralMass(massHeavy), label.MzFromNeutralMass(massLight)); Assert.IsTrue(Adduct.PossibleAdductDescriptionStart("[")); Assert.IsTrue(Adduct.PossibleAdductDescriptionStart("M")); Assert.IsTrue(Adduct.PossibleAdductDescriptionStart("[2M+CH3COO]")); Assert.AreEqual(Adduct.FromStringAssumeProtonated("M+2CH3COO"), Adduct.FromStringAssumeProtonated("M+CH3COO").ChangeCharge(-2)); Assert.AreEqual(Adduct.FromStringAssumeProtonated("M-2CH3COO"), Adduct.FromStringAssumeProtonated("M+CH3COO").ChangeCharge(2)); Assert.AreEqual(Adduct.FromStringAssumeProtonated("M-2Na"), Adduct.FromStringAssumeProtonated("M+Na").ChangeCharge(-2)); Assert.AreEqual(Adduct.FromStringAssumeProtonated("M+Na"), Adduct.FromStringAssumeProtonated("M+Na").ChangeCharge(1)); Assert.AreEqual(Adduct.FromStringAssumeProtonated("M+2Na"), Adduct.FromStringAssumeProtonated("M+Na").ChangeCharge(2)); Assert.AreEqual(Adduct.FromStringAssumeProtonated("M+2Na"), Adduct.FromStringAssumeProtonated("M+3Na").ChangeCharge(2)); Assert.AreEqual(Adduct.FromStringAssumeProtonated("M2Cl37+2Na"), Adduct.FromStringAssumeProtonated("M2Cl37+3Na").ChangeCharge(2)); AssertEx.ThrowsException <InvalidOperationException>(() => Adduct.FromStringAssumeProtonated("M+2Na-H").ChangeCharge(2)); // Too complex to adjust formula AssertEx.ThrowsException <InvalidOperationException>(() => Adduct.FromStringAssumeProtonatedNonProteomic("[M2")); // Seen in the wild, wasn't handled well Assert.AreEqual(Adduct.FromStringAssumeProtonated("M++++").AdductCharge, Adduct.FromChargeNoMass(4).AdductCharge); Assert.AreEqual(Adduct.FromStringAssumeProtonated("M+4"), Adduct.FromChargeNoMass(4)); Assert.AreEqual(Adduct.FromStringAssumeProtonated("M--").AdductCharge, Adduct.FromChargeNoMass(-2).AdductCharge); Assert.AreEqual(Adduct.FromStringAssumeProtonated("M-2"), Adduct.FromChargeNoMass(-2)); Assert.IsTrue(ReferenceEquals(Adduct.FromStringAssumeChargeOnly("M-"), Adduct.FromChargeNoMass(-1))); // Both should return Adduct.M_MINUS Assert.IsTrue(ReferenceEquals(Adduct.FromStringAssumeChargeOnly("M+"), Adduct.FromChargeNoMass(1))); // Both should return Adduct.M_PLUS Assert.IsTrue(ReferenceEquals(Adduct.FromStringAssumeChargeOnly("[M+]"), Adduct.FromChargeNoMass(1))); // Both should return Adduct.M_PLUS Assert.IsTrue(ReferenceEquals(Adduct.FromStringAssumeChargeOnly("M-H"), Adduct.M_MINUS_H)); Assert.IsTrue(ReferenceEquals(Adduct.FromStringAssumeChargeOnly("M+H"), Adduct.M_PLUS_H)); var a = Adduct.FromChargeProtonated(-1); var aa = Adduct.FromStringAssumeProtonated("M+CH3COO"); var b = Adduct.FromChargeProtonated(2); var bb = Adduct.FromChargeProtonated(2); var bbb = Adduct.FromChargeProtonated(2); var bbbb = Adduct.FromChargeProtonated(2); var c = Adduct.FromChargeProtonated(3); var cc = Adduct.FromStringAssumeChargeOnly("M+3H"); var ccc = Adduct.FromStringAssumeChargeOnly("[M+3H]"); Assert.AreEqual(a.AdductCharge, aa.AdductCharge); Assert.IsTrue(b == bb); Assert.IsTrue(b == bbb); Assert.IsTrue(ReferenceEquals(bbb, bbbb)); Assert.IsTrue(c.AdductCharge == cc.AdductCharge); Assert.IsFalse(c == cc); Assert.IsTrue(c != cc); Assert.IsTrue(cc == ccc); Assert.IsTrue(a < aa); Assert.IsTrue(a < b); Assert.IsTrue(b > a); Assert.IsTrue(b != a); var sorted = new List <Adduct> { a, aa, b, bb, bbb, bbbb, c, cc, ccc }; var unsorted = new List <Adduct> { bb, aa, ccc, b, c, bbb, a, bbbb, cc }; Assert.IsFalse(sorted.SequenceEqual(unsorted)); unsorted.Sort(); Assert.IsTrue(sorted.SequenceEqual(unsorted)); var ints = new AdductMap <int>(); Assert.AreEqual(0, ints[a]); ints[a] = 7; Assert.AreEqual(7, ints[a]); var adducts = new AdductMap <Adduct>(); Assert.AreEqual(null, adducts[a]); adducts[a] = b; Assert.AreEqual(b, adducts[a]); adducts[a] = c; Assert.AreEqual(c, adducts[a]); var d = Adduct.FromStringAssumeProtonated("[2M+3H]"); var dd = Adduct.FromStringAssumeProtonated("[M+3H]"); var ddd = Adduct.FromStringAssumeProtonated("[M-Na]"); Assert.IsTrue(d.ChangeMassMultiplier(1).SameEffect(dd)); Assert.IsTrue(dd.ChangeMassMultiplier(2).SameEffect(d)); Assert.AreEqual(dd.ChangeIonFormula("-Na"), ddd); Assert.AreEqual(d.ChangeMassMultiplier(1).ChangeIonFormula("-Na"), ddd); CheckLabel(BioMassCalc.Cl37); CheckLabel(BioMassCalc.Br81); CheckLabel(BioMassCalc.S33); CheckLabel(BioMassCalc.S34); CheckLabel(BioMassCalc.P32); CheckLabel(BioMassCalc.C14); CheckLabel(BioMassCalc.O17); CheckLabel(BioMassCalc.O18); var tips = Adduct.Tips; foreach (var nickname in Adduct.DICT_ADDUCT_NICKNAMES) { Assert.IsTrue(tips.Contains(nickname.Key)); } foreach (var nickname in Adduct.DICT_ADDUCT_ISOTOPE_NICKNAMES) { Assert.IsTrue(tips.Contains(nickname.Key)); } }
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 string _unlabledFormula; // Chemical formula after adduct application and stripping of labels /// <summary> /// Constructs an IonInfo, which holds a neutral formula and adduct, or possibly just a chemical formula if no adduct is included in the description /// </summary> public IonInfo(string formulaWithOptionalAdduct, Adduct adduct) { Formula = formulaWithOptionalAdduct + adduct.AdductFormula; }
/// <summary> /// For test purposes /// </summary> public static double CalculateIonMass(TypedMass mass, Adduct adduct) { return(adduct.ApplyToMass(mass)); }
/// <summary> /// For test purposes /// </summary> public static double CalculateIonMz(TypedMass mass, Adduct adduct) { return(adduct.MzFromNeutralMass(mass)); }
/// <summary> /// For test purposes /// </summary> public double CalculateIonMz(string desc, Adduct adduct) { var mass = CalculateMassFromFormula(desc); return(adduct.MzFromNeutralMass(mass)); }
public IList <int> ShowIonCharges(IEnumerable <Adduct> adductPriority) { return(Adduct.OrderedAbsoluteChargeValues(adductPriority).ToList()); }
public void UpdateUI(bool selectionChanged = true) { // Only worry about updates, if the graph is visible // And make sure it is not disposed, since rendering happens on a timer if (!Visible || IsDisposed) { return; } // Clear existing data from the graph pane var graphPane = (MSGraphPane)graphControl.MasterPane[0]; graphPane.CurveList.Clear(); graphPane.GraphObjList.Clear(); GraphItem = null; GraphHelper.FormatGraphPane(graphControl.GraphPane); GraphHelper.FormatFontSize(graphControl.GraphPane, Settings.Default.SpectrumFontSize); // Try to find a tree node with spectral library info associated // with the current selection. var nodeTree = _stateProvider.SelectedNode as SrmTreeNode; var nodeGroupTree = nodeTree as TransitionGroupTreeNode; var nodeTranTree = nodeTree as TransitionTreeNode; if (nodeTranTree != null) { nodeGroupTree = nodeTranTree.Parent as TransitionGroupTreeNode; } var nodeGroup = (nodeGroupTree != null ? nodeGroupTree.DocNode : null); PeptideTreeNode nodePepTree; if (nodeGroup == null) { nodePepTree = nodeTree as PeptideTreeNode; if (nodePepTree != null) { var listInfoGroups = GetLibraryInfoChargeGroups(nodePepTree); if (listInfoGroups.Length == 1) { nodeGroup = listInfoGroups[0]; } else if (listInfoGroups.Length > 1) { _nodeGroup = null; toolBar.Visible = false; _graphHelper.SetErrorGraphItem(new NoDataMSGraphItem( Resources.GraphSpectrum_UpdateUI_Multiple_charge_states_with_library_spectra)); return; } } } else { nodePepTree = nodeGroupTree.Parent as PeptideTreeNode; } // Check for appropriate spectrum to load SrmSettings settings = DocumentUI.Settings; PeptideLibraries libraries = settings.PeptideSettings.Libraries; bool available = false; if (nodeGroup == null || (!nodeGroup.HasLibInfo && !libraries.HasMidasLibrary)) { _spectra = null; } else { TransitionGroup group = nodeGroup.TransitionGroup; TransitionDocNode transition = (nodeTranTree == null ? null : nodeTranTree.DocNode); var lookupSequence = group.Peptide.Target;// Sequence or custom ion id ExplicitMods lookupMods = null; if (nodePepTree != null) { lookupSequence = nodePepTree.DocNode.SourceUnmodifiedTarget; lookupMods = nodePepTree.DocNode.SourceExplicitMods; } try { // Try to load a list of spectra matching the criteria for // the current node group. if (libraries.HasLibraries && libraries.IsLoaded) { if (NodeGroupChanged(nodeGroup)) { try { UpdateSpectra(nodeGroup, lookupSequence, lookupMods); UpdateToolbar(); } catch (Exception) { _spectra = null; UpdateToolbar(); throw; } _nodeGroup = nodeGroup; if (settings.TransitionSettings.Instrument.IsDynamicMin) { ZoomSpectrumToSettings(); } } var spectrum = SelectedSpectrum; if (spectrum != null) { IsotopeLabelType typeInfo = spectrum.LabelType; var types = _stateProvider.ShowIonTypes(group.IsProteomic); var adducts = (group.IsProteomic ? Transition.DEFAULT_PEPTIDE_LIBRARY_CHARGES : nodeGroup.InUseAdducts).ToArray(); var charges = _stateProvider.ShowIonCharges(adducts); var rankTypes = group.IsProteomic ? settings.TransitionSettings.Filter.PeptideIonTypes : settings.TransitionSettings.Filter.SmallMoleculeIonTypes; var rankAdducts = group.IsProteomic ? settings.TransitionSettings.Filter.PeptideProductCharges : settings.TransitionSettings.Filter.SmallMoleculeFragmentAdducts; var rankCharges = Adduct.OrderedAbsoluteChargeValues(rankAdducts); // Make sure the types and charges in the settings are at the head // of these lists to give them top priority, and get rankings correct. int i = 0; foreach (IonType type in rankTypes) { if (types.Remove(type)) { types.Insert(i++, type); } } i = 0; var showAdducts = new List <Adduct>(); foreach (var charge in rankCharges) { if (charges.Remove(charge)) { charges.Insert(i++, charge); } // NB for all adducts we just look at abs value of charge // CONSIDER(bspratt): we may want finer per-adduct control for small molecule use showAdducts.AddRange(adducts.Where(a => charge == Math.Abs(a.AdductCharge))); } showAdducts.AddRange(adducts.Where(a => charges.Contains(Math.Abs(a.AdductCharge)) && !showAdducts.Contains(a))); SpectrumPeaksInfo spectrumInfo = spectrum.SpectrumPeaksInfo; var spectrumInfoR = new LibraryRankedSpectrumInfo(spectrumInfo, typeInfo, nodeGroup, settings, lookupSequence, lookupMods, showAdducts, types, rankAdducts, rankTypes); GraphItem = new SpectrumGraphItem(nodeGroup, transition, spectrumInfoR, spectrum.LibName) { ShowTypes = types, ShowCharges = charges, ShowRanks = Settings.Default.ShowRanks, ShowMz = Settings.Default.ShowIonMz, ShowObservedMz = Settings.Default.ShowObservedMz, ShowDuplicates = Settings.Default.ShowDuplicateIons, FontSize = Settings.Default.SpectrumFontSize, LineWidth = Settings.Default.SpectrumLineWidth }; LibraryChromGroup chromatogramData = null; if (Settings.Default.ShowLibraryChromatograms) { chromatogramData = spectrum.LoadChromatogramData(); } if (null == chromatogramData) { _graphHelper.ResetForSpectrum(new[] { nodeGroup.TransitionGroup }); _graphHelper.AddSpectrum(GraphItem); _graphHelper.ZoomSpectrumToSettings(DocumentUI, nodeGroup); } else { _graphHelper.ResetForChromatograms(new[] { nodeGroup.TransitionGroup }); var displayType = GraphChromatogram.GetDisplayType(DocumentUI, nodeGroup); IList <TransitionDocNode> displayTransitions = GraphChromatogram.GetDisplayTransitions(nodeGroup, displayType).ToArray(); int numTrans = displayTransitions.Count; var allChromDatas = chromatogramData.ChromDatas.Where( chromData => DisplayTypeMatches(chromData, displayType)).ToList(); var chromDatas = new List <LibraryChromGroup.ChromData>(); for (int iTran = 0; iTran < numTrans; iTran++) { var displayTransition = displayTransitions[iTran]; var indexMatch = allChromDatas.IndexOf(chromData => IonMatches(displayTransition.Transition, chromData)); if (indexMatch >= 0) { chromDatas.Add(allChromDatas[indexMatch]); allChromDatas.RemoveAt(indexMatch); } else { chromDatas.Add(null); } } allChromDatas.Sort((chromData1, chromData2) => chromData1.Mz.CompareTo(chromData2.Mz)); chromDatas.AddRange(allChromDatas); double maxHeight = chromDatas.Max(chromData => null == chromData ? double.MinValue : chromData.Height); int iChromDataPrimary = chromDatas.IndexOf(chromData => null != chromData && maxHeight == chromData.Height); int colorOffset = displayType == DisplayTypeChrom.products ? GraphChromatogram.GetDisplayTransitions(nodeGroup, DisplayTypeChrom. precursors).Count() : 0; for (int iChromData = 0; iChromData < chromDatas.Count; iChromData++) { var chromData = chromDatas[iChromData]; if (chromData == null) { continue; } string label; var pointAnnotation = GraphItem.AnnotatePoint(new PointPair(chromData.Mz, 1.0)); if (null != pointAnnotation) { label = pointAnnotation.Label; } else { label = chromData.Mz.ToString(@"0.####"); } TransitionDocNode matchingTransition; Color color; if (iChromData < numTrans) { matchingTransition = displayTransitions[iChromData]; color = GraphChromatogram.COLORS_LIBRARY[ (iChromData + colorOffset) % GraphChromatogram.COLORS_LIBRARY.Count]; } else { matchingTransition = null; color = GraphChromatogram.COLORS_GROUPS[ iChromData % GraphChromatogram.COLORS_GROUPS.Count]; } TransitionChromInfo tranPeakInfo; ChromatogramInfo chromatogramInfo; MakeChromatogramInfo(nodeGroup.PrecursorMz, chromatogramData, chromData, out chromatogramInfo, out tranPeakInfo); var graphItem = new ChromGraphItem(nodeGroup, matchingTransition, chromatogramInfo, iChromData == iChromDataPrimary ? tranPeakInfo : null, null, new[] { iChromData == iChromDataPrimary }, null, 0, false, false, null, 0, color, Settings.Default.ChromatogramFontSize, 1); LineItem curve = (LineItem)_graphHelper.AddChromatogram(PaneKey.DEFAULT, graphItem); if (matchingTransition == null) { curve.Label.Text = label; } curve.Line.Width = Settings.Default.ChromatogramLineWidth; if (null != transition) { if (IonMatches(transition.Transition, chromData)) { color = ChromGraphItem.ColorSelected; } } curve.Color = color; } graphPane.Title.IsVisible = false; graphPane.Legend.IsVisible = true; _graphHelper.FinishedAddingChromatograms(chromatogramData.StartTime, chromatogramData.EndTime, false); graphControl.Refresh(); } graphControl.IsEnableVPan = graphControl.IsEnableVZoom = !Settings.Default.LockYAxis; available = true; } } } catch (Exception) { _graphHelper.SetErrorGraphItem(new NoDataMSGraphItem( Resources.GraphSpectrum_UpdateUI_Failure_loading_spectrum__Library_may_be_corrupted)); return; } } // Show unavailable message, if no spectrum loaded if (!available) { UpdateToolbar(); _nodeGroup = null; _graphHelper.SetErrorGraphItem(new UnavailableMSGraphItem()); } }