Skip to content

Commit

Permalink
IEquality hot fix hot fix (#819)
Browse files Browse the repository at this point in the history
* Added parent accession check back to PeptideWithSetMods

* Added Test

* oopsies

* Refactor Equals methods and add IEquatable regions

Removed Equals from IBioPolymerWithSetMods interface in Omics.
Updated Equals in PeptideWithSetModifications to check OneBasedStartResidue and Parent?.Accession.
Updated Equals in OligoWithSetMods to check FullSequence and DigestionParams?.DigestionAgent.
Added IEquatable regions to PeptideWithSetModifications and OligoWithSetMods.
Organized Equals methods with #region IEquatable directives.

* workflow edit

* Even more equality tetsts

* adjusted oligo with set mods

* ugh

---------

Co-authored-by: Nic Bollis <[email protected]>
Co-authored-by: Alex <[email protected]>
  • Loading branch information
3 people authored Dec 18, 2024
1 parent 90bd259 commit dc44773
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 45 deletions.
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

0 comments on commit dc44773

Please sign in to comment.