Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MatchedFragmentIonl and Product enhancement #838

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 25 additions & 22 deletions mzLib/Omics/Fragmentation/MatchedFragmentIon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace Omics.Fragmentation
{
public class MatchedFragmentIon
public class MatchedFragmentIon : IEquatable<MatchedFragmentIon>
{
public readonly Product NeutralTheoreticalProduct;
public readonly double Mz;
Expand All @@ -20,27 +20,15 @@ public MatchedFragmentIon(Product neutralTheoreticalProduct, double experMz, dou
Intensity = experIntensity;
Charge = charge;
}
public double MassErrorDa
{
get
{
return Mz.ToMass(Charge) - NeutralTheoreticalProduct.NeutralMass;
}
}

public double MassErrorPpm
{
get
{
return (MassErrorDa / NeutralTheoreticalProduct.NeutralMass) * 1e6;
}
}

public string Annotation
public bool IsInternalFragment => NeutralTheoreticalProduct.IsInternalFragment;
public virtual double MassErrorDa => Mz.ToMass(Charge) - NeutralTheoreticalProduct.NeutralMass;
public virtual double MassErrorPpm => MassErrorDa / NeutralTheoreticalProduct.NeutralMass * 1e6;
public virtual string Annotation
{
get
{
StringBuilder sb = new StringBuilder();
StringBuilder sb = new StringBuilder(8);

bool containsNeutralLoss = NeutralTheoreticalProduct.NeutralLoss != 0;

Expand Down Expand Up @@ -77,13 +65,12 @@ public override string ToString()
// Rounding to 6 decimal places ensures accurate comparison up to 1,000,000,000 AU (non-inclusive)
internal const int IntensityDecimalDigits = 6;

public override bool Equals(object obj)
public override bool Equals(object? obj)
{
return obj is MatchedFragmentIon otherIon && this.Equals(otherIon);
}


public bool Equals(MatchedFragmentIon other)
public bool Equals(MatchedFragmentIon? other)
{
return this.NeutralTheoreticalProduct.Equals(other.NeutralTheoreticalProduct)
&& this.Charge == other.Charge
Expand All @@ -100,4 +87,20 @@ public override int GetHashCode()
Math.Round(Intensity, IntensityDecimalDigits).GetHashCode());
}
}
}

/// <summary>
/// Represents a matched fragment ion with additional caching capabilities.
/// Inherits from <see cref="MatchedFragmentIon"/>.
/// </summary>
public class MatchedFragmentIonWithCache(Product neutralTheoreticalProduct, double experMz, double experIntensity, int charge)
: MatchedFragmentIon(neutralTheoreticalProduct, experMz, experIntensity, charge)
{
private double? _massErrorDa = null!;
private double? _massErrorPpm = null!;
private string? _annotation = null!;

public override string Annotation => _annotation ??= base.Annotation;
public override double MassErrorDa => _massErrorDa ??= base.MassErrorDa;
public override double MassErrorPpm => _massErrorPpm ??= base.MassErrorPpm;
}
}
22 changes: 19 additions & 3 deletions mzLib/Omics/Fragmentation/Product.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class Product : IHasMass, IEquatable<Product>
public ProductType? SecondaryProductType { get; } //used for internal fragment ions
public int SecondaryFragmentNumber { get; } //used for internal fragment ions
public double MonoisotopicMass => NeutralMass;
public bool IsInternalFragment => SecondaryProductType != null;

/// <summary>
/// A product is the individual neutral fragment from an MS dissociation. A fragmentation product here contains one of the two termini (N- or C-).
Expand All @@ -31,15 +32,17 @@ public Product(ProductType productType, FragmentationTerminus terminus, double n
Terminus = terminus;
FragmentNumber = fragmentNumber;
ResiduePosition = residuePosition;

// These two are set for internal ions only.
SecondaryProductType = secondaryProductType;
SecondaryFragmentNumber = secondaryFragmentNumber;
}

public string Annotation
public virtual string Annotation
{
get
{
StringBuilder sb = new StringBuilder();
StringBuilder sb = new StringBuilder(8);

if (SecondaryProductType == null)
{
Expand Down Expand Up @@ -84,7 +87,7 @@ public override string ToString()
}
}

public override bool Equals(object obj)
public override bool Equals(object? obj)
{
return obj is Product other && Equals(other);
}
Expand All @@ -108,4 +111,17 @@ public override int GetHashCode()
return NeutralMass.GetHashCode();
}
}


/// <summary>
/// Product Class that caches the annotation information to avoid repeated calculations
/// </summary>
public class ProductWithCache(ProductType productType, FragmentationTerminus terminus, double neutralMass, int fragmentNumber,
int residuePosition, double neutralLoss, ProductType? secondaryProductType = null, int secondaryFragmentNumber = 0)
: Product(productType, terminus, neutralMass, fragmentNumber, residuePosition, neutralLoss,
secondaryProductType, secondaryFragmentNumber)
{
private string? _annotation;
public override string Annotation => _annotation ??= base.Annotation;
}
}
4 changes: 2 additions & 2 deletions mzLib/Omics/SpectrumMatch/SpectrumMatchFromTsv.cs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ protected static List<MatchedFragmentIon> ReadFragmentIonsFromString(string matc
double neutralTheoreticalMass = neutralExperimentalMass - errorDa; //theoretical mass is measured mass - measured error

//The product created here is the theoretical product, with the mass back-calculated from the measured mass and measured error
Product theoreticalProduct = new Product(productType,
Product theoreticalProduct = new ProductWithCache(productType,
terminus,
neutralTheoreticalMass,
fragmentNumber,
Expand All @@ -259,7 +259,7 @@ protected static List<MatchedFragmentIon> ReadFragmentIonsFromString(string matc
secondaryProductType,
secondaryFragmentNumber);

matchedIons.Add(new MatchedFragmentIon(theoreticalProduct, mz, intensity, z));
matchedIons.Add(new MatchedFragmentIonWithCache(theoreticalProduct, mz, intensity, z));
}
}
return matchedIons;
Expand Down
16 changes: 8 additions & 8 deletions mzLib/Test/FileReadingTests/TestPsmFromTsv.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,14 +170,14 @@ public static void TestParseModification()

// psm with single modificaiton
PsmFromTsv singleMod = psms[0];
var modDict = Omics.SpectrumMatch.SpectrumMatchFromTsv.ParseModifications(singleMod.FullSequence);
var modDict = SpectrumMatchFromTsv.ParseModifications(singleMod.FullSequence);
Assert.That(modDict.Count == 1);
Assert.That(modDict.ContainsKey(37));
Assert.That(modDict.Values.First().Contains("Common Fixed:Carbamidomethyl on C"));

// psm with two modifications
PsmFromTsv twoMods = psms[15];
modDict = Omics.SpectrumMatch.SpectrumMatchFromTsv.ParseModifications(twoMods.FullSequence);
modDict = SpectrumMatchFromTsv.ParseModifications(twoMods.FullSequence);
Assert.That(modDict.Count == 2);
Assert.That(modDict.ContainsKey(0) && modDict.ContainsKey(104));
Assert.That(modDict[0].Count == 1);
Expand All @@ -188,7 +188,7 @@ public static void TestParseModification()

// psm with two mods on the same amino acid
string fullSeq = "[Common Fixed:Carbamidomethyl on C]|[UniProt:N-acetylserine on S]KPRKIEEIKDFLLTARRKDAKSVKIKKNKDNVKFK";
modDict = Omics.SpectrumMatch.SpectrumMatchFromTsv.ParseModifications(fullSeq);
modDict = SpectrumMatchFromTsv.ParseModifications(fullSeq);
Assert.That(modDict.Count == 1);
Assert.That(modDict.ContainsKey(0));
Assert.That(modDict[0].Count == 2);
Expand All @@ -202,23 +202,23 @@ public static void TestRemoveSpecialCharacters()
// successful removal of the default character
string toRemove = "ANDVHAO|CNVASDF|ABVCUAE";
int length = toRemove.Length;
Omics.SpectrumMatch.SpectrumMatchFromTsv.RemoveSpecialCharacters(ref toRemove);
SpectrumMatchFromTsv.RemoveSpecialCharacters(ref toRemove);
Assert.That(toRemove.Length == length - 2);
Assert.That(toRemove.Equals("ANDVHAOCNVASDFABVCUAE"));

// does not remove default character when prompted otherwise
toRemove = "ANDVHAO|CNVASDF|ABVCUAE";
Omics.SpectrumMatch.SpectrumMatchFromTsv.RemoveSpecialCharacters(ref toRemove, specialCharacter: @"\[");
SpectrumMatchFromTsv.RemoveSpecialCharacters(ref toRemove, specialCharacter: @"\[");
Assert.That(toRemove.Length == length);
Assert.That(toRemove.Equals("ANDVHAO|CNVASDF|ABVCUAE"));

// replaces default symbol when prompted
Omics.SpectrumMatch.SpectrumMatchFromTsv.RemoveSpecialCharacters(ref toRemove, replacement: @"%");
SpectrumMatchFromTsv.RemoveSpecialCharacters(ref toRemove, replacement: @"%");
Assert.That(toRemove.Length == length);
Assert.That(toRemove.Equals("ANDVHAO%CNVASDF%ABVCUAE"));

// replaces inputted symbol with non-default symbol
Omics.SpectrumMatch.SpectrumMatchFromTsv.RemoveSpecialCharacters(ref toRemove, replacement: @"=", specialCharacter: @"%");
SpectrumMatchFromTsv.RemoveSpecialCharacters(ref toRemove, replacement: @"=", specialCharacter: @"%");
Assert.That(toRemove.Length == length);
Assert.That(toRemove.Equals("ANDVHAO=CNVASDF=ABVCUAE"));
}
Expand All @@ -242,7 +242,7 @@ public static void TestToString()
public static void TestParenthesesRemovalForSilac()
{
string baseSequence = "ASDF(+8.01)ASDF";
string cleanedSequence = Omics.SpectrumMatch.SpectrumMatchFromTsv.RemoveParentheses(baseSequence);
string cleanedSequence = SpectrumMatchFromTsv.RemoveParentheses(baseSequence);
Assert.IsTrue(cleanedSequence.Equals("ASDFASDF"));
}

Expand Down
144 changes: 144 additions & 0 deletions mzLib/Test/Omics/MatchedFragmentIonTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
using System;
using Omics.Fragmentation;
using NUnit.Framework;
using System.Diagnostics.CodeAnalysis;

namespace Test.Omics
{
[ExcludeFromCodeCoverage]
public class MatchedFragmentIonTests
{
[Test]
public void TestMatchedFragmentIonConstructor()
{
var product = new Product(ProductType.b, FragmentationTerminus.N, 100.0, 1, 1, 0.0);
var ion = new MatchedFragmentIon(product, 101.0, 200.0, 1);

Assert.That(ion.NeutralTheoreticalProduct, Is.EqualTo(product));
Assert.That(ion.Mz, Is.EqualTo(101.0));
Assert.That(ion.Intensity, Is.EqualTo(200.0));
Assert.That(ion.Charge, Is.EqualTo(1));
}

[Test]
public void TestIsInternalFragment()
{
var product = new Product(ProductType.b, FragmentationTerminus.N, 100.0, 1, 1, 0.0, ProductType.aStar, 2);
var ion = new MatchedFragmentIon(product, 101.0, 200.0, 1);

Assert.That(ion.IsInternalFragment, Is.True);

product = new ProductWithCache(ProductType.b, FragmentationTerminus.N, 100.0, 1, 1, 0.0, ProductType.aStar, 2);
ion = new MatchedFragmentIonWithCache(product, 101.0, 200.0, 1);

Assert.That(ion.IsInternalFragment, Is.True);
}

[Test]
public void TestMassErrorDa()
{
var product = new Product(ProductType.b, FragmentationTerminus.N, 100.0, 1, 1, 0.0);
var ion = new MatchedFragmentIon(product, 101.0, 200.0, 1);
Assert.That(ion.MassErrorDa, Is.EqualTo(1.0).Within(10));

product = new Product(ProductType.b, FragmentationTerminus.N, 100.0, 1, 1, 0.0);
ion = new MatchedFragmentIonWithCache(product, 101.0, 200.0, 1);
Assert.That(ion.MassErrorDa, Is.EqualTo(1.0).Within(10));
Assert.That(ion.MassErrorDa, Is.EqualTo(1.0).Within(10));

product = new ProductWithCache(ProductType.b, FragmentationTerminus.N, 100.0, 1, 1, 0.0);
ion = new MatchedFragmentIon(product, 101.0, 200.0, 1);
Assert.That(ion.MassErrorDa, Is.EqualTo(1.0).Within(10));
Assert.That(ion.MassErrorDa, Is.EqualTo(1.0).Within(10));

product = new ProductWithCache(ProductType.b, FragmentationTerminus.N, 100.0, 1, 1, 0.0);
ion = new MatchedFragmentIonWithCache(product, 101.0, 200.0, 1);
Assert.That(ion.MassErrorDa, Is.EqualTo(1.0).Within(10));
Assert.That(ion.MassErrorDa, Is.EqualTo(1.0).Within(10));
}

[Test]
public void TestMassErrorPpm()
{
var product = new Product(ProductType.b, FragmentationTerminus.N, 100.0, 1, 1, 0.0);
var ion = new MatchedFragmentIon(product, 101.0, 200.0, 1);
Assert.That(ion.MassErrorPpm, Is.EqualTo(-72.764).Within(0.001));

product = new Product(ProductType.b, FragmentationTerminus.N, 100.0, 1, 1, 0.0);
ion = new MatchedFragmentIonWithCache(product, 101, 200.0, 1);
Assert.That(ion.MassErrorPpm, Is.EqualTo(-72.764).Within(0.001));
Assert.That(ion.MassErrorPpm, Is.EqualTo(-72.764).Within(0.001));

product = new ProductWithCache(ProductType.b, FragmentationTerminus.N, 100.0, 1, 1, 0.0);
ion = new MatchedFragmentIon(product, 101, 200.0, 1);
ion = new MatchedFragmentIon(product, 101, 200.0, 1);
Assert.That(ion.MassErrorPpm, Is.EqualTo(-72.764).Within(0.001));

product = new ProductWithCache(ProductType.b, FragmentationTerminus.N, 100.0, 1, 1, 0.0);
ion = new MatchedFragmentIonWithCache(product, 101, 200.0, 1);
Assert.That(ion.MassErrorPpm, Is.EqualTo(-72.764).Within(0.001));
Assert.That(ion.MassErrorPpm, Is.EqualTo(-72.764).Within(0.001));
}

[Test]
public void TestAnnotation()
{
var product = new Product(ProductType.b, FragmentationTerminus.N, 100.0, 1, 1, 0.0);
var ion = new MatchedFragmentIon(product, 101.0, 200.0, 1);

Assert.That(ion.Annotation, Is.EqualTo("b1+1"));
}

[Test]
public void TestToString()
{
var product = new Product(ProductType.b, FragmentationTerminus.N, 100.0, 1, 1, 0.0);
var ion = new MatchedFragmentIon(product, 101.0, 200.0, 1);

Assert.That(ion.ToString(), Is.EqualTo("b1+1\t;100"));

Product P = new Product(ProductType.b, FragmentationTerminus.N, 1, 1, 1, 0);
MatchedFragmentIon m = new MatchedFragmentIon(P, 1, 1, 1);
Assert.That(m.ToString(), Is.EqualTo("b1+1\t;1"));
}

[Test]
public void TestEquals()
{
var product = new Product(ProductType.b, FragmentationTerminus.N, 100.0, 1, 1, 0.0);
var ion1 = new MatchedFragmentIon(product, 101.0, 200.0, 1);
var ion2 = new MatchedFragmentIon(product, 101.0, 200.0, 1);

Assert.That(ion1.Equals(ion2), Is.True);
}

[Test]
public static void TestMatchedFragmentAndCachedIonEquals()
{
Product P = new Product(ProductType.b, FragmentationTerminus.N, 1, 1, 1, 0);
MatchedFragmentIon ion1 = new MatchedFragmentIon(P, experMz: 150, experIntensity: 99.99999999999, charge: 2);
MatchedFragmentIon ion2 = new MatchedFragmentIonWithCache(P, experMz: 149.99999999999, experIntensity: 100, charge: 2);
Assert.That(ion1, Is.EqualTo(ion2));
}

[Test]
public void TestGetHashCode()
{
var product = new Product(ProductType.b, FragmentationTerminus.N, 100.0, 1, 1, 0.0);
var ion = new MatchedFragmentIon(product, 101.0, 200.0, 1);

Assert.That(ion.GetHashCode(), Is.EqualTo(HashCode.Combine(product.GetHashCode(), 1.GetHashCode(), Math.Round(101.0, 10).GetHashCode(), Math.Round(200.0, 6).GetHashCode())));
}

[Test]
public static void Test_MatchedFragmentGetHashCode()
{
Product P = new Product(ProductType.b, FragmentationTerminus.N, 1, 1, 1, 0);
Product pPrime = new Product(ProductType.b, FragmentationTerminus.N, 1, 1, 1, 0);
MatchedFragmentIon m = new MatchedFragmentIon(P, 1, 1, 1);
MatchedFragmentIon mPrime = new MatchedFragmentIonWithCache(pPrime, 1, 1, 1);
Assert.That(P.GetHashCode(), Is.EqualTo(pPrime.GetHashCode()));
Assert.That(mPrime.GetHashCode(), Is.EqualTo(m.GetHashCode()));
}
}
}
Loading
Loading