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

IEquality hot fix hot fix #819

Merged
merged 9 commits into from
Dec 18, 2024
Merged
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
23 changes: 0 additions & 23 deletions mzLib/Omics/IBioPolymerWithSetMods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,29 +44,6 @@ public interface IBioPolymerWithSetMods : IHasChemicalFormula, IEquatable<IBioPo
char this[int zeroBasedIndex] => BaseSequence[zeroBasedIndex];
IBioPolymer Parent { get; }

/// <summary>
/// Default Equals Method for IBioPolymerWithSetMods
/// </summary>
/// <param name="other"></param>
/// <returns></returns>
/// <remarks>
/// Different parent but same sequence and digestion condition => are equal
/// Different Digestion agent but same sequence => are not equal (this is for multi-protease analysis in MetaMorpheus)
/// </remarks>
bool IEquatable<IBioPolymerWithSetMods>.Equals(IBioPolymerWithSetMods? other)
{
if (other is null) return false;
if (ReferenceEquals(this, other)) return true;
if (other.GetType() != GetType()) return false;

// for those constructed from sequence and mods only
if (Parent is null && other.Parent is null)
return FullSequence.Equals(other.FullSequence);

return FullSequence == other.FullSequence
&& Equals(DigestionParams?.DigestionAgent, other.DigestionParams?.DigestionAgent);
}

public void Fragment(DissociationType dissociationType, FragmentationTerminus fragmentationTerminus,
List<Product> products);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ namespace Proteomics.ProteolyticDigestion
[Serializable]
public class PeptideWithSetModifications : ProteolyticPeptide, IBioPolymerWithSetMods, IEquatable<PeptideWithSetModifications>
{
public string FullSequence { get; private set; } //sequence with modifications
public string FullSequence { get; init; } //sequence with modifications
public int NumFixedMods { get; }
// Parameter to store the full sequence of the corresponding Target or Decoy peptide
// If the peptide in question is a decoy, this pairs it to the target it was generated from
Expand Down Expand Up @@ -884,6 +884,11 @@ public override string ToString()
return FullSequence + string.Join("\t", AllModsOneIsNterminus.Select(m => m.ToString()));
}

#region IEquatable

/// <summary>
/// Peptides are equal if they have the same full sequence, parent, and digestion agent
/// </summary>
public override bool Equals(object obj)
{
if (obj is PeptideWithSetModifications peptide)
Expand All @@ -893,12 +898,29 @@ public override bool Equals(object obj)
return false;
}

/// <summary>
/// Peptides are equal if they have the same full sequence, parent, and digestion agent
/// </summary>
public bool Equals(IBioPolymerWithSetMods other) => Equals(other as PeptideWithSetModifications);

/// <summary>
/// Peptides are equal if they have the same full sequence, parent, and digestion agent
/// </summary>
public bool Equals(PeptideWithSetModifications other)
{
// interface equals first because it does null and reference checks
return (this as IBioPolymerWithSetMods).Equals(other)
if (other is null) return false;
if (ReferenceEquals(this, other)) return true;
if (other.GetType() != GetType()) return false;

// for those constructed from sequence and mods only
if (Parent is null && other.Parent is null)
return FullSequence.Equals(other.FullSequence);

return FullSequence == other.FullSequence
&& Equals(DigestionParams?.DigestionAgent, other.DigestionParams?.DigestionAgent)
// These last two are important for parsimony in MetaMorpheus
&& OneBasedStartResidue == other!.OneBasedStartResidue
&& Equals(Parent, other.Parent);
&& Equals(Parent?.Accession, other.Parent?.Accession);
}

public override int GetHashCode()
Expand All @@ -917,6 +939,8 @@ public override int GetHashCode()
return hash.ToHashCode();
}

#endregion

/// <summary>
/// This should be run after deserialization of a PeptideWithSetModifications, in order to set its Protein and Modification objects, which were not serialized
/// </summary>
Expand Down
41 changes: 41 additions & 0 deletions mzLib/Test/TestPeptideWithSetMods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ public static void TestPeptideOligoEquality()
Assert.That(!peptide.Equals(oligo));
Assert.That(!((IBioPolymerWithSetMods)oligo).Equals(peptide));
Assert.That(!((IBioPolymerWithSetMods)peptide).Equals(oligo));
Assert.That(!((object)oligo).Equals(peptide));
Assert.That(!((object)peptide).Equals(oligo));
}

[Test]
Expand Down Expand Up @@ -1194,6 +1196,45 @@ public static void TestPeptideWithSetModsNoParentProtein()
Assert.AreEqual('-', last.NextResidue);
}

[Test]
public static void TestPeptideWithSetModsEquals()
{
// Create two proteins
Protein protein1 = new Protein("SEQUENCEK", "accession1");
Protein protein2 = new Protein("SEQUENCEK", "accession2");

// Create digestion parameters
DigestionParams digestionParams = new DigestionParams(protease: "trypsin", maxMissedCleavages: 0, initiatorMethionineBehavior: InitiatorMethionineBehavior.Retain);

// Digest the proteins to get peptides
PeptideWithSetModifications peptide1 = protein1.Digest(digestionParams, new List<Modification>(), new List<Modification>()).First();
PeptideWithSetModifications peptide2 = protein2.Digest(digestionParams, new List<Modification>(), new List<Modification>()).First();

// Test equality - same peptide
Assert.IsTrue(peptide1.Equals(peptide1));

// different peptide
Assert.IsTrue(!peptide1.Equals(peptide2));
Assert.IsTrue(!peptide1.Equals((object)peptide2));
Assert.IsTrue(!peptide1.Equals((IBioPolymerWithSetMods)peptide2));
Assert.AreNotEqual(peptide1.GetHashCode(), peptide2.GetHashCode());

// Test inequality with different start residue
PeptideWithSetModifications peptide3 = new PeptideWithSetModifications(protein1, digestionParams, 2, 9, CleavageSpecificity.Full, "", 0, new Dictionary<int, Modification>(), 0);
Assert.IsFalse(peptide1.Equals(peptide3));

// Test inequality with different parent accession
PeptideWithSetModifications peptide4 = new PeptideWithSetModifications(protein2, digestionParams, 1, 9, CleavageSpecificity.Full, "", 0, new Dictionary<int, Modification>(), 0);
Assert.IsFalse(peptide1.Equals(peptide4));

// all fail on null
Assert.That(!peptide1.Equals(null));
Assert.That(!peptide1.Equals((object)null));
Assert.That(!peptide1.Equals((PeptideWithSetModifications)null));
}



[Test]
public static void TestIBioPolymerWithSetModsModificationFromFullSequence()
{
Expand Down
40 changes: 26 additions & 14 deletions mzLib/Test/Transcriptomics/TestOligoWithSetMods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,43 +91,55 @@ public static void TestEquality()
.Digest(new RnaDigestionParams(), [], [])
.ElementAt(1);

Assert.That(oligoWithSetMods, Is.EqualTo(oligoWithSetMods2));
// same oligos
Assert.That(oligoWithSetMods.Equals(oligoWithSetMods2));
Assert.That(oligoWithSetMods.Equals((object)oligoWithSetMods2));
Assert.That(oligoWithSetMods.Equals((OligoWithSetMods)oligoWithSetMods2));
Assert.That(oligoWithSetMods.Equals(oligoWithSetMods));
Assert.That(oligoWithSetMods.Equals((object)oligoWithSetMods));
Assert.That(oligoWithSetMods.Equals((OligoWithSetMods)oligoWithSetMods));
Assert.That(oligoWithSetMods.GetHashCode(), Is.EqualTo(oligoWithSetMods2.GetHashCode()));
Assert.That(oligoWithSetMods.Equals((object)oligoWithSetMods2)); // Test the Equals(Object obj) method

// all fail on null
Assert.That(!oligoWithSetMods2.Equals(null));
Assert.That(!oligoWithSetMods2.Equals((object)null));
Assert.That(!oligoWithSetMods2.Equals((OligoWithSetMods)null));

// Null parent checks
oligoWithSetMods = new(oligoWithSetMods.FullSequence, modDict.ToDictionary(p => p.Value.First().IdWithMotif, p => p.Value.First()));
oligoWithSetMods2 = new OligoWithSetMods(oligoWithSetMods.FullSequence, modDict.ToDictionary(p => p.Value.First().IdWithMotif, p => p.Value.First()));
var oligoWithSetMods3 = new OligoWithSetMods(oligoWithSetMods.FullSequence + "AGAUA", modDict.ToDictionary(p => p.Value.First().IdWithMotif, p => p.Value.First()));

Assert.That(oligoWithSetMods, Is.EqualTo(oligoWithSetMods2));
Assert.That(oligoWithSetMods, Is.EqualTo((object)oligoWithSetMods2));
Assert.That(oligoWithSetMods, Is.EqualTo((OligoWithSetMods)oligoWithSetMods2));
Assert.That(oligoWithSetMods, Is.Not.EqualTo(oligoWithSetMods3));
Assert.That(oligoWithSetMods, Is.Not.EqualTo((object)oligoWithSetMods3));
Assert.That(oligoWithSetMods, Is.Not.EqualTo((IBioPolymerWithSetMods)oligoWithSetMods3));
// same oligo null parent
Assert.That(oligoWithSetMods.Equals(oligoWithSetMods2));
Assert.That(oligoWithSetMods.Equals((object)oligoWithSetMods2));
Assert.That(oligoWithSetMods.Equals((OligoWithSetMods)oligoWithSetMods2));

// different oligo null parent
Assert.That(!oligoWithSetMods.Equals(oligoWithSetMods3));
Assert.That(!oligoWithSetMods.Equals((object)oligoWithSetMods3));
Assert.That(!oligoWithSetMods.Equals((IBioPolymerWithSetMods)oligoWithSetMods3));
}

[Test]
[TestCase("GUACUG", "GUACUGGUACUG", "RNase A")]
[TestCase("GUAGGAG", "GUAGCAG", "RNase A")]
public static void TestEquality_DifferentParentSameDigestionProduct(string sequence1, string sequence2, string enzyme)
public static void TestInequality_DifferentParentSameDigestionProduct(string sequence1, string sequence2, string enzyme)
{
var digestionParams = new RnaDigestionParams(rnase: enzyme, minLength: 1, maxMissedCleavages: 0);

var oligo1 = new RNA(sequence1)
var oligo1 = new RNA(sequence1, "", "rna1", "", "")
.Digest(digestionParams, [], [])
.First();

var oligo2 = new RNA(sequence2)
var oligo2 = new RNA(sequence2, "", "rna3", "", "")
.Digest(digestionParams, [], [])
.First();

Assert.That(oligo1, Is.EqualTo(oligo2));
Assert.That(oligo1, Is.Not.EqualTo(oligo2));
Assert.That(oligo1.Equals(oligo1));
Assert.That(oligo1, Is.EqualTo((object)oligo2));
Assert.That(oligo1.GetHashCode(), Is.EqualTo(oligo2.GetHashCode()));
Assert.That(oligo1, Is.Not.EqualTo((object)oligo2));
Assert.That(oligo1.GetHashCode(), Is.Not.EqualTo(oligo2.GetHashCode()));
}

/// <summary>
Expand Down
33 changes: 32 additions & 1 deletion mzLib/Transcriptomics/Digestion/OligoWithSetMods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,11 @@ public void Fragment(DissociationType dissociationType, FragmentationTerminus fr
products.AddRange(GetNeutralFragments(type, sequence));
}

#region IEquatable

/// <summary>
/// Oligos are equal if they have the same full sequence, parent, and digestion agent, and terminal caps
/// </summary>
public override bool Equals(object? obj)
{
if (obj is OligoWithSetMods oligo)
Expand All @@ -224,9 +229,31 @@ public override bool Equals(object? obj)
return false;
}

/// <summary>
/// Oligos are equal if they have the same full sequence, parent, and digestion agent, and terminal caps
/// </summary>
public bool Equals(IBioPolymerWithSetMods? other) => Equals(other as OligoWithSetMods);

/// <summary>
/// Oligos are equal if they have the same full sequence, parent, and digestion agent, and terminal caps
/// </summary>
public bool Equals(OligoWithSetMods? other)
{
return (this as IBioPolymerWithSetMods).Equals(other);
if (other is null) return false;
if (ReferenceEquals(this, other)) return true;
if (other.GetType() != GetType()) return false;

// for those constructed from sequence and mods only
if (Parent is null && other.Parent is null)
return FullSequence.Equals(other.FullSequence);

return FullSequence == other.FullSequence
&& Equals(DigestionParams?.DigestionAgent, other.DigestionParams?.DigestionAgent)
&& _fivePrimeTerminus.Equals(other._fivePrimeTerminus)
&& _threePrimeTerminus.Equals(other._threePrimeTerminus)
// These last two are important for parsimony in MetaMorpheus
&& OneBasedStartResidue == other!.OneBasedStartResidue
&& Equals(Parent?.Accession, other.Parent?.Accession);
}

public override int GetHashCode()
Expand All @@ -242,9 +269,13 @@ public override int GetHashCode()
{
hash.Add(DigestionParams.DigestionAgent);
}
hash.Add(FivePrimeTerminus);
hash.Add(ThreePrimeTerminus);
return hash.ToHashCode();
}

#endregion

/// <summary>
/// Generates theoretical internal fragments for given dissociation type for this peptide.
/// The "products" parameter is filled with these fragments.
Expand Down
6 changes: 3 additions & 3 deletions mzLib/Transcriptomics/RNA.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public RNA(string sequence, IHasChemicalFormula? fivePrimeTerm = null, IHasChemi
/// </summary>
/// <param name="sequence"></param>
/// <param name="name"></param>
/// <param name="identifier"></param>
/// <param name="accession"></param>
/// <param name="organism"></param>
/// <param name="databaseFilePath"></param>
/// <param name="fivePrimeTerminus"></param>
Expand All @@ -33,12 +33,12 @@ public RNA(string sequence, IHasChemicalFormula? fivePrimeTerm = null, IHasChemi
/// <param name="isDecoy"></param>
/// <param name="geneNames"></param>
/// <param name="databaseAdditionalFields"></param>
public RNA(string sequence, string name, string identifier, string organism, string databaseFilePath,
public RNA(string sequence, string name, string accession, string organism, string databaseFilePath,
IHasChemicalFormula? fivePrimeTerminus = null, IHasChemicalFormula? threePrimeTerminus = null,
IDictionary<int, List<Modification>>? oneBasedPossibleModifications = null,
bool isContaminant = false, bool isDecoy = false, List<Tuple<string, string>> geneNames = null,
Dictionary<string, string>? databaseAdditionalFields = null)
: base(sequence, name, identifier, organism, databaseFilePath, fivePrimeTerminus, threePrimeTerminus,
: base(sequence, name, accession, organism, databaseFilePath, fivePrimeTerminus, threePrimeTerminus,
oneBasedPossibleModifications, isContaminant, isDecoy, geneNames, databaseAdditionalFields)
{

Expand Down
1 change: 1 addition & 0 deletions mzLib/mzLib.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=Modomics/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Nucleolytic/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Oligo/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Oligos/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Prsm/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Regexes/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Toppic/@EntryIndexedValue">True</s:Boolean>
Expand Down
Loading