diff --git a/Framework/API/inc/MantidAPI/LogManager.h b/Framework/API/inc/MantidAPI/LogManager.h
index 577e64a38872ac201ac285fdfe81e26f862c9d2c..f5c49e6d9ee07fb1aa068422cd0780cbf5311818 100644
--- a/Framework/API/inc/MantidAPI/LogManager.h
+++ b/Framework/API/inc/MantidAPI/LogManager.h
@@ -182,6 +182,9 @@ public:
   /// Clear the logs
   void clearLogs();
 
+  bool operator==(const LogManager &other) const;
+  bool operator!=(const LogManager &other) const;
+
 protected:
   /// Load the run from a NeXus file with a given group name
   void loadNexus(::NeXus::File *file,
diff --git a/Framework/API/inc/MantidAPI/Run.h b/Framework/API/inc/MantidAPI/Run.h
index 747140a27e9d4c96765150c40a2b6183aab6b7a6..568c25dbdce5ac9da5f4e207d8c240af1dfa320e 100644
--- a/Framework/API/inc/MantidAPI/Run.h
+++ b/Framework/API/inc/MantidAPI/Run.h
@@ -41,6 +41,8 @@ public:
   Run(const Run &other);
   ~Run();
   Run &operator=(const Run &other);
+  bool operator==(const Run &other);
+  bool operator!=(const Run &other);
 
   /// Clone
   boost::shared_ptr<Run> clone();
diff --git a/Framework/API/inc/MantidAPI/Sample.h b/Framework/API/inc/MantidAPI/Sample.h
index dc70c2391fa669bfebce66789eef89b700e5092b..7d5c79373ead04be140ea3b95081af07784ab75f 100644
--- a/Framework/API/inc/MantidAPI/Sample.h
+++ b/Framework/API/inc/MantidAPI/Sample.h
@@ -116,6 +116,9 @@ public:
   /// Delete the oriented lattice
   void clearOrientedLattice();
 
+  bool operator==(const Sample &other) const;
+  bool operator!=(const Sample &other) const;
+
 private:
   /// The sample name
   std::string m_name;
diff --git a/Framework/API/src/LogManager.cpp b/Framework/API/src/LogManager.cpp
index 4f6b9d41c5d6e3b56e608c6ebe57b7305b09bd47..451dbe145c054f9b8410274373ae344359de9d75 100644
--- a/Framework/API/src/LogManager.cpp
+++ b/Framework/API/src/LogManager.cpp
@@ -549,6 +549,14 @@ void LogManager::loadNexus(::NeXus::File *file,
  */
 void LogManager::clearLogs() { m_manager->clear(); }
 
+bool LogManager::operator==(const LogManager &other) const {
+  return *m_manager == *(other.m_manager);
+}
+
+bool LogManager::operator!=(const LogManager &other) const {
+  return *m_manager != *(other.m_manager);
+}
+
 //-----------------------------------------------------------------------------------------------------------------------
 // Private methods
 //-----------------------------------------------------------------------------------------------------------------------
diff --git a/Framework/API/src/Run.cpp b/Framework/API/src/Run.cpp
index f178c78c5eaf7df021bfa2fd66c6071e1201068a..7818d5770453811dde3a64d40b6f101d658d5620 100644
--- a/Framework/API/src/Run.cpp
+++ b/Framework/API/src/Run.cpp
@@ -62,6 +62,14 @@ Run &Run::operator=(const Run &other) {
   return *this;
 }
 
+bool Run::operator==(const Run &other) {
+  return *m_goniometer == *other.m_goniometer &&
+         LogManager::operator==(other) &&
+         this->m_histoBins == other.m_histoBins;
+}
+
+bool Run::operator!=(const Run &other) { return !this->operator==(other); }
+
 boost::shared_ptr<Run> Run::clone() {
   auto clone = boost::make_shared<Run>();
   for (auto property : this->m_manager->getProperties()) {
diff --git a/Framework/API/src/Sample.cpp b/Framework/API/src/Sample.cpp
index b26a1a04bafcfe80d37d1636cde7ddfa7e3900b1..fb301a0b13eb2637338cd2db0b3cdb702353bfd8 100644
--- a/Framework/API/src/Sample.cpp
+++ b/Framework/API/src/Sample.cpp
@@ -423,5 +423,36 @@ void Sample::clearOrientedLattice() {
     m_lattice.reset(nullptr);
   }
 }
+
+bool Sample::operator==(const Sample &other) const {
+  if (m_samples.size() != other.m_samples.size())
+    return false;
+  for (size_t i = 0; i < m_samples.size(); ++i) {
+    if (*m_samples[i] != *other.m_samples[i])
+      return false;
+  }
+  auto compare = [](const auto &a, const auto &b, auto call_on) {
+    // both null or both not null
+    if (bool(a) ^ bool(b))
+      return false;
+    if (a)
+      return call_on(a) == call_on(b);
+    else
+      return true;
+  };
+  return *m_lattice == *other.m_lattice && this->m_name == other.m_name &&
+         this->m_height == other.m_height && this->m_width == other.m_width &&
+         this->m_thick == other.m_thick && m_geom_id == other.m_geom_id &&
+         compare(m_environment, other.m_environment,
+                 [](const auto &x) { return x->name(); }) &&
+         compare(m_shape, other.m_shape,
+                 [](const auto &x) { return x->shape(); }) &&
+         compare(m_crystalStructure, other.m_crystalStructure,
+                 [](const auto &x) { return *(x->spaceGroup()); });
+}
+
+bool Sample::operator!=(const Sample &other) const {
+  return !this->operator==(other);
+}
 } // namespace API
 } // namespace Mantid
diff --git a/Framework/API/test/LogManagerTest.h b/Framework/API/test/LogManagerTest.h
index c047bd8e1e057e7196d78b95760644e810fd1ff1..9710ebb4fab940a10fe48852ea98f9dd06fc30ae 100644
--- a/Framework/API/test/LogManagerTest.h
+++ b/Framework/API/test/LogManagerTest.h
@@ -28,6 +28,7 @@ namespace {
 class ConcreteProperty : public Property {
 public:
   ConcreteProperty() : Property("Test", typeid(int)) {}
+  ConcreteProperty(std::string name) : Property(name, typeid(int)) {}
   ConcreteProperty *clone() const override {
     return new ConcreteProperty(*this);
   }
@@ -35,15 +36,21 @@ public:
   std::string getDefault() const override {
     return "getDefault() is not implemented in this class";
   }
-  std::string value() const override { return "Nothing"; }
+  std::string value() const override { return m_value; }
   Json::Value valueAsJson() const override { return Json::Value(); }
-  std::string setValue(const std::string &) override { return ""; }
+  std::string setValue(const std::string &value) override {
+    m_value = value;
+    return m_value;
+  }
   std::string setValueFromJson(const Json::Value &) override { return ""; }
   std::string setValueFromProperty(const Property &) override { return ""; }
   std::string setDataItem(const boost::shared_ptr<DataItem>) override {
     return "";
   }
   Property &operator+=(Property const *) override { return *this; }
+
+private:
+  std::string m_value = "Nothing";
 };
 
 template <typename T>
@@ -185,7 +192,7 @@ public:
     runInfo.addProperty(p);
 
     TS_ASSERT_EQUALS(runInfo.getMemorySize(),
-                     sizeof(ConcreteProperty) + sizeof(void *));
+                     p->getMemorySize() + sizeof(Property *));
   }
 
   void test_GetTimeSeriesProperty_Returns_TSP_When_Log_Exists() {
@@ -530,6 +537,48 @@ public:
     run3.loadNexus(th.file.get(), "");
   }
 
+  void test_operator_equals() {
+    LogManager a;
+    LogManager b;
+    a.addProperty(std::make_unique<ConcreteProperty>());
+    b.addProperty(std::make_unique<ConcreteProperty>());
+    TS_ASSERT_EQUALS(a, b);
+    TS_ASSERT(!(a != b));
+  }
+
+  void test_not_equals_when_number_of_entries_differ() {
+    LogManager a;
+    LogManager b;
+    a.addProperty(std::make_unique<ConcreteProperty>("a1"));
+    b.addProperty(std::make_unique<ConcreteProperty>("b1"));
+    b.addProperty(std::make_unique<ConcreteProperty>("b2"));
+    TS_ASSERT_DIFFERS(a, b);
+    TS_ASSERT(!(a == b));
+  }
+
+  void test_not_equals_when_values_differ() {
+    LogManager a;
+    LogManager b;
+    auto prop1 = std::make_unique<ConcreteProperty>();
+    auto prop2 = std::make_unique<ConcreteProperty>();
+    prop2->setValue("another_value");
+    a.addProperty(std::move(prop1));
+    b.addProperty(std::move(prop2));
+    TS_ASSERT_DIFFERS(a, b);
+    TS_ASSERT(!(a == b));
+  }
+
+  void test_not_equals_when_keys_differ() {
+    LogManager a;
+    LogManager b;
+    auto prop1 = std::make_unique<ConcreteProperty>("Temp");
+    auto prop2 = std::make_unique<ConcreteProperty>("Pressure");
+    a.addProperty(std::move(prop1));
+    b.addProperty(std::move(prop2));
+    TS_ASSERT_DIFFERS(a, b);
+    TS_ASSERT(!(a == b));
+  }
+
 private:
   template <typename T>
   void doTest_GetPropertyAsSingleValue_SingleType(const T value) {
diff --git a/Framework/API/test/RunTest.h b/Framework/API/test/RunTest.h
index a41f319a7eae852b0a4d9b5ebdca618e2a06d844..d9489980074ef7997216ea2a0de1a61c728ba23b 100644
--- a/Framework/API/test/RunTest.h
+++ b/Framework/API/test/RunTest.h
@@ -609,6 +609,65 @@ public:
     TS_ASSERT_DELTA(run3.getProtonCharge(), 1.234, 1e-5);
   }
 
+  void test_equals_when_runs_empty() {
+    Run a{};
+    Run b{a};
+    TS_ASSERT_EQUALS(a, b);
+  }
+
+  void test_equals_when_runs_identical() {
+    Run a{};
+    a.addProperty(std::make_unique<ConcreteProperty>());
+    const DblMatrix rotation_x{
+        {1, 0, 0, 0, 0, -1, 0, 1, 0}}; // 90 degrees around x axis
+    a.setGoniometer(Goniometer{rotation_x}, false /*do not use log angles*/);
+    a.storeHistogramBinBoundaries({1, 2, 3, 4});
+    Run b{a};
+    TS_ASSERT_EQUALS(a, b);
+    TS_ASSERT(!(a != b));
+  }
+
+  void test_not_equal_when_histogram_bin_boundaries_differ() {
+    Run a{};
+    a.addProperty(std::make_unique<ConcreteProperty>());
+    DblMatrix rotation_x{
+        {1, 0, 0, 0, 0, -1, 0, 1, 0}}; // 90 degrees around x axis
+    a.setGoniometer(Goniometer{rotation_x}, false /*do not use log angles*/);
+    a.storeHistogramBinBoundaries({1, 2, 3, 4});
+    Run b{a};
+    b.storeHistogramBinBoundaries({0, 2, 3, 4});
+    TS_ASSERT_DIFFERS(a, b);
+    TS_ASSERT(!(a == b));
+  }
+
+  void test_not_equal_when_properties_differ() {
+    Run a{};
+    a.addProperty(std::make_unique<ConcreteProperty>());
+    DblMatrix rotation_x{
+        {1, 0, 0, 0, 0, -1, 0, 1, 0}}; // 90 degrees around x axis
+    a.setGoniometer(Goniometer{rotation_x}, false /*do not use log angles*/);
+    a.storeHistogramBinBoundaries({1, 2, 3, 4});
+    Run b{a};
+    b.removeProperty("Test");
+    TS_ASSERT_DIFFERS(a, b);
+    TS_ASSERT(!(a == b));
+  }
+
+  void test_not_equal_when_goniometer_differ() {
+    Run a{};
+    a.addProperty(std::make_unique<ConcreteProperty>());
+    DblMatrix rotation_x{
+        {1, 0, 0, 0, 0, -1, 0, 1, 0}}; // 90 degrees around x axis
+    a.setGoniometer(Goniometer{rotation_x}, false /*do not use log angles*/);
+    a.storeHistogramBinBoundaries({1, 2, 3, 4});
+    Run b{a};
+    Mantid::Kernel::DblMatrix rotation_y{
+        {0, 0, 1, 0, 1, 0, -1, 0, 0}}; // 90 degrees around y axis
+    b.setGoniometer(Goniometer{rotation_y}, false /*do not use log angles*/);
+    TS_ASSERT_DIFFERS(a, b);
+    TS_ASSERT(!(a == b));
+  }
+
 private:
   /// Testing bins
   std::vector<double> m_test_energy_bins;
diff --git a/Framework/API/test/SampleTest.h b/Framework/API/test/SampleTest.h
index b4583c3705678cf5d5f5aa5bb30e8d16f934710b..5092ea50bc342541e680c637e660736cd9e6cf9a 100644
--- a/Framework/API/test/SampleTest.h
+++ b/Framework/API/test/SampleTest.h
@@ -359,4 +359,126 @@ public:
 
     TS_ASSERT(loaded.getName().empty());
   }
+
+  void test_equal_when_sample_identical() {
+    Sample a;
+    Sample b;
+    TS_ASSERT_EQUALS(a, b);
+  }
+
+  void test_not_equal_when_sample_differs_in_extents() {
+    Sample a;
+    auto b = a;
+    a.setHeight(10);
+    TS_ASSERT_DIFFERS(a, b);
+    TS_ASSERT(!(a == b));
+    b = a;
+    a.setWidth(10);
+    TS_ASSERT_DIFFERS(a, b);
+    TS_ASSERT(!(a == b));
+    b = a;
+    a.setThickness(10);
+    TS_ASSERT_DIFFERS(a, b);
+    TS_ASSERT(!(a == b));
+  }
+
+  void test_not_equal_when_sample_differs_in_geom_id() {
+    Sample a;
+    auto b = a;
+    TS_ASSERT_EQUALS(a, b);
+    a.setGeometryFlag(1);
+    b.setGeometryFlag(2);
+    TS_ASSERT_DIFFERS(a, b);
+    TS_ASSERT(!(a == b));
+  }
+  void test_not_equal_when_sample_differs_in_name() {
+    Sample a;
+    auto b = a;
+    b.setName("something");
+    TS_ASSERT_DIFFERS(a, b);
+    TS_ASSERT(!(a == b));
+  }
+
+  void test_not_equal_when_sample_differs_in_environment() {
+    auto kit1 = std::make_unique<SampleEnvironment>(
+        "Env", boost::make_shared<const Container>(""));
+
+    auto kit2 = std::make_unique<SampleEnvironment>(
+        "Env2", boost::make_shared<const Container>(""));
+
+    // same as kit1
+    auto kit3 = std::make_unique<SampleEnvironment>(
+        kit1->name(), boost::make_shared<const Container>(""));
+
+    Sample a;
+    auto b = a;
+    b.setEnvironment(std::move(kit1));
+    // A has no environment
+    TS_ASSERT_DIFFERS(a, b);
+    TS_ASSERT(!(a == b));
+
+    // A has valid but different same environment
+    a.setEnvironment(std::move(kit2));
+    TS_ASSERT_DIFFERS(a, b);
+    TS_ASSERT(!(a == b));
+
+    // A has valid but different same environment
+    a.setEnvironment(std::move(kit3));
+    TS_ASSERT_EQUALS(a, b);
+    TS_ASSERT(!(a != b));
+  }
+
+  void test_not_equal_when_sample_differs_in_shape() {
+    IObject_sptr shape1 = ComponentCreationHelper::createCappedCylinder(
+        0.0127, 1.0, V3D(), V3D(0.0, 1.0, 0.0), "cyl");
+
+    IObject_sptr shape2 = ComponentCreationHelper::createCappedCylinder(
+        0.0137, 1.0, V3D(), V3D(0.0, 0.0, 0.0), "cyl");
+
+    Sample a;
+    auto b = a;
+    a.setShape(shape1);
+    // b has no shape
+    TS_ASSERT_DIFFERS(a, b);
+    TS_ASSERT(!(a == b));
+
+    // b has different shape
+    b.setShape(shape2);
+    TS_ASSERT_DIFFERS(a, b);
+    TS_ASSERT(!(a == b));
+
+    // b has same shape
+    b.setShape(IObject_sptr(shape1->clone()));
+    TS_ASSERT_EQUALS(a, b);
+    TS_ASSERT(!(a != b));
+  }
+
+  void test_not_equal_when_sample_differs_in_space_group() {
+    CrystalStructure structure1("3 4 5 90 90 90", "C m m m",
+                                "Fe 0.12 0.23 0.121");
+    // Same as above
+    CrystalStructure structure2("3 4 5 90 90 90", "C m m m",
+                                "Fe 0.12 0.23 0.121");
+    // Different
+    CrystalStructure structure3("5.431 5.431 5.431", "F d -3 m",
+                                "Si 0 0 0 1.0 0.02");
+
+    Sample a;
+    auto b = a;
+    // b has no structure
+    a.setCrystalStructure(structure1);
+    TS_ASSERT_DIFFERS(a, b);
+    TS_ASSERT(!(a == b));
+
+    // b has different structure
+    b.setCrystalStructure(structure3);
+    TS_ASSERT_DIFFERS(a, b);
+    TS_ASSERT(!(a == b));
+
+    // b has same structure
+    b = Sample{};
+    b.setCrystalStructure(structure2);
+    TS_ASSERT_EQUALS(a, b);
+    TS_ASSERT(!(a != b));
+  }
 };
diff --git a/Framework/Geometry/inc/MantidGeometry/Crystal/OrientedLattice.h b/Framework/Geometry/inc/MantidGeometry/Crystal/OrientedLattice.h
index b39fd0944206398502bfea2aec605272e10a7700..4334d1b7c97411c161ce2e6019cc2b8c513ffdc7 100644
--- a/Framework/Geometry/inc/MantidGeometry/Crystal/OrientedLattice.h
+++ b/Framework/Geometry/inc/MantidGeometry/Crystal/OrientedLattice.h
@@ -67,6 +67,9 @@ public:
   static bool GetABC(const Kernel::DblMatrix &UB, Kernel::V3D &a_dir,
                      Kernel::V3D &b_dir, Kernel::V3D &c_dir);
 
+  bool operator==(const OrientedLattice &other) const;
+  bool operator!=(const OrientedLattice &other) const;
+
 private:
   Kernel::DblMatrix U;
   Kernel::DblMatrix UB;
@@ -79,4 +82,4 @@ private:
   void recalculate() override;
 };
 } // namespace Geometry
-} // namespace Mantid
\ No newline at end of file
+} // namespace Mantid
diff --git a/Framework/Geometry/inc/MantidGeometry/Crystal/UnitCell.h b/Framework/Geometry/inc/MantidGeometry/Crystal/UnitCell.h
index c84c586d813e4212fa89432fcb48f75d79c9284f..b34c2a0dbfc5d0847a4318cf2d5c888193de4d21 100644
--- a/Framework/Geometry/inc/MantidGeometry/Crystal/UnitCell.h
+++ b/Framework/Geometry/inc/MantidGeometry/Crystal/UnitCell.h
@@ -161,6 +161,8 @@ public:
   double volume() const;
   double recVolume() const;
   virtual void recalculateFromGstar(const Kernel::Matrix<double> &NewGstar);
+  bool operator==(const UnitCell &other) const;
+  bool operator!=(const UnitCell &other) const;
 
 protected:
   /// Lattice parameter a,b,c,alpha,beta,gamma (in \f$ \mbox{ \AA } \f$ and
diff --git a/Framework/Geometry/inc/MantidGeometry/Instrument/Goniometer.h b/Framework/Geometry/inc/MantidGeometry/Instrument/Goniometer.h
index e09c1b7301e6ba95ea4f81b8f1150747d972a45f..86069b5781d9e86706a2de8c58b45fbe47c4a88f 100644
--- a/Framework/Geometry/inc/MantidGeometry/Instrument/Goniometer.h
+++ b/Framework/Geometry/inc/MantidGeometry/Instrument/Goniometer.h
@@ -97,6 +97,8 @@ public:
   void loadNexus(::NeXus::File *file, const std::string &group);
   /// the method reports if the goniometer was defined with some parameters
   bool isDefined() const;
+  bool operator==(const Goniometer &other) const;
+  bool operator!=(const Goniometer &other) const;
 
 private:
   /// Global rotation matrix of the goniometer
diff --git a/Framework/Geometry/src/Crystal/OrientedLattice.cpp b/Framework/Geometry/src/Crystal/OrientedLattice.cpp
index dae0f1c61d6fa37e297c4f1772a42190b735c925..94284f3413f0c3be9425c20c0c78807119804785 100644
--- a/Framework/Geometry/src/Crystal/OrientedLattice.cpp
+++ b/Framework/Geometry/src/Crystal/OrientedLattice.cpp
@@ -344,5 +344,11 @@ void OrientedLattice::recalculate() {
   UnitCell::recalculate();
   UB = U * getB();
 }
+bool OrientedLattice::operator==(const OrientedLattice &other) const {
+  return UB == other.UB;
+}
+bool OrientedLattice::operator!=(const OrientedLattice &other) const {
+  return UB != other.UB;
+}
 } // Namespace Geometry
 } // Namespace Mantid
diff --git a/Framework/Geometry/src/Crystal/UnitCell.cpp b/Framework/Geometry/src/Crystal/UnitCell.cpp
index c1e93394240a49e0c26a7adba44a90872f319673..3a7feab47aa4d1665c9ceefdf1dd49f5ae4a1f50 100644
--- a/Framework/Geometry/src/Crystal/UnitCell.cpp
+++ b/Framework/Geometry/src/Crystal/UnitCell.cpp
@@ -880,6 +880,13 @@ void UnitCell::recalculateFromGstar(const DblMatrix &NewGstar) {
   calculateB();
 }
 
+bool UnitCell::operator==(const UnitCell &other) const {
+  return da == other.da; // da error not used in comparison
+}
+bool UnitCell::operator!=(const UnitCell &other) const {
+  return !this->operator==(other);
+}
+
 std::ostream &operator<<(std::ostream &out, const UnitCell &unitCell) {
   // always show the lattice constants
   out << "Lattice Parameters:" << std::fixed << std::setprecision(6)
diff --git a/Framework/Geometry/src/Instrument/Goniometer.cpp b/Framework/Geometry/src/Instrument/Goniometer.cpp
index e2dd92bd4764f54b2ec037d0586a12a739f0c9fe..4fda260a72865f7c6e8859db079d7ad0e6d17318 100644
--- a/Framework/Geometry/src/Instrument/Goniometer.cpp
+++ b/Framework/Geometry/src/Instrument/Goniometer.cpp
@@ -92,6 +92,14 @@ void Goniometer::setR(Kernel::DblMatrix rot) {
 /// Function reports if the goniometer is defined
 bool Goniometer::isDefined() const { return initFromR || (!motors.empty()); }
 
+bool Goniometer::operator==(const Goniometer &other) const {
+  return this->R == other.R;
+}
+
+bool Goniometer::operator!=(const Goniometer &other) const {
+  return this->R != other.R;
+}
+
 /// Return information about axes.
 /// @return str :: string that contains on each line one motor information (axis
 /// name, direction, sense, angle)
diff --git a/Framework/Geometry/test/GoniometerTest.h b/Framework/Geometry/test/GoniometerTest.h
index 3059f0495ddf205bda8d0f344b50e3083c46251a..0b05a15a8989a5147ecad833339432fb2ed0ac6e 100644
--- a/Framework/Geometry/test/GoniometerTest.h
+++ b/Framework/Geometry/test/GoniometerTest.h
@@ -255,4 +255,24 @@ public:
     // Rotation matrices should be the same after loading
     TS_ASSERT_EQUALS(G2.getR(), G.getR());
   }
+
+  void test_equals_when_identical() {
+    Mantid::Kernel::DblMatrix rotation_x{
+        {1, 0, 0, 0, 0, -1, 0, 1, 0}}; // 90 degrees around x axis
+    Goniometer a(rotation_x);
+    Goniometer b(rotation_x);
+    TS_ASSERT_EQUALS(a, b);
+    TS_ASSERT(!(a != b));
+  }
+
+  void test_not_equals_when_not_identical() {
+    Mantid::Kernel::DblMatrix rotation_x{
+        {1, 0, 0, 0, 0, -1, 0, 1, 0}}; // 90 degrees around x axis
+    Mantid::Kernel::DblMatrix rotation_y{
+        {0, 0, 1, 0, 1, 0, -1, 0, 0}}; // 90 degrees around y axis
+    Goniometer a(rotation_x);
+    Goniometer b(rotation_y);
+    TS_ASSERT_DIFFERS(a, b);
+    TS_ASSERT(!(a == b));
+  }
 };
diff --git a/Framework/Geometry/test/OrientedLatticeTest.h b/Framework/Geometry/test/OrientedLatticeTest.h
index b1f9379f2ea05d3c5c8971a4039c94a37428768a..be341d5de64aec2786f9dcd1d2d1346198a86cf6 100644
--- a/Framework/Geometry/test/OrientedLatticeTest.h
+++ b/Framework/Geometry/test/OrientedLatticeTest.h
@@ -300,4 +300,29 @@ public:
     TSM_ASSERT_EQUALS(" should be along the y direction", V3D(0, 1, 0), ey);
     TSM_ASSERT_EQUALS("y direction is", V3D(0, -1, 0), eyPrime);
   }
+  void test_equals_when_orientedlattice_identical() {
+    OrientedLattice a(1.0, 2.0, 3.0, 90.0, 90.0,
+                      90.0); // create via lattice parameters
+    OrientedLattice b{a};
+    TS_ASSERT_EQUALS(a, b);
+    TS_ASSERT(!(a != b));
+  }
+
+  void test_not_equals_when_orientedlattice_different_b() {
+    OrientedLattice a(1.0, 2.0, 3.0, 90.0, 90.0,
+                      90.0); // create via lattice parameters
+    OrientedLattice b{a};
+    b.seta(10); // Results in B change
+    TS_ASSERT_DIFFERS(a, b);
+    TS_ASSERT(!(a == b));
+  }
+
+  void test_not_equals_when_orientedlattice_different_u() {
+    OrientedLattice a(1.0, 2.0, 3.0, 90.0, 90.0,
+                      90.0); // create via lattice parameters
+    OrientedLattice b{a};
+    b.setUFromVectors(V3D(0, 1, 0), V3D(1, 0, 0)); // Different U
+    TS_ASSERT_DIFFERS(a, b);
+    TS_ASSERT(!(a == b));
+  }
 };
diff --git a/Framework/Geometry/test/UnitCellTest.h b/Framework/Geometry/test/UnitCellTest.h
index 45f02dd43b70d34f8f6545ab9973fe294bdc71ce..8450c8c973eb4ef74c2b7ec4ab93e91c37886750 100644
--- a/Framework/Geometry/test/UnitCellTest.h
+++ b/Framework/Geometry/test/UnitCellTest.h
@@ -170,4 +170,53 @@ public:
     TS_ASSERT_DIFFERS(precisionLimit.c(), precisionLimitOther.c());
     TS_ASSERT_DELTA(precisionLimit.c(), precisionLimitOther.c(), 1e-9);
   }
+
+  void test_equals_when_unitcell_identical() {
+    const UnitCell a(2.0, 4.0, 5.0, 90.0, 100.0, 102.0);
+    const UnitCell b(a);
+    TS_ASSERT_EQUALS(a, b);
+    TS_ASSERT(!(a != b));
+  }
+
+  void test_not_equals_when_unitcell_differs_in_a() {
+    const UnitCell a(1.0, 1.0, 1.0, 90.0, 90.0, 90.0);
+    UnitCell b(a);
+    b.seta(2.0);
+    TS_ASSERT_DIFFERS(a, b);
+  }
+
+  void test_not_equals_when_unitcell_differs_in_b() {
+    const UnitCell a(1.0, 1.0, 1.0, 90.0, 90.0, 90.0);
+    UnitCell b(a);
+    b.setb(2.0);
+    TS_ASSERT_DIFFERS(a, b);
+  }
+
+  void test_not_equals_when_unitcell_differs_in_c() {
+    const UnitCell a(1.0, 1.0, 1.0, 90.0, 90.0, 90.0);
+    UnitCell b(a);
+    b.setc(2.0);
+    TS_ASSERT_DIFFERS(a, b);
+  }
+
+  void test_not_equals_when_unitcell_differs_in_alpha() {
+    const UnitCell a(1.0, 1.0, 1.0, 90.0, 90.0, 90.0);
+    UnitCell b(a);
+    b.setalpha(100);
+    TS_ASSERT_DIFFERS(a, b);
+  }
+
+  void test_not_equals_when_unitcell_differs_in_beta() {
+    const UnitCell a(1.0, 1.0, 1.0, 90.0, 90.0, 90.0);
+    UnitCell b(a);
+    b.setbeta(100);
+    TS_ASSERT_DIFFERS(a, b);
+  }
+
+  void test_not_equals_when_unitcell_differs_in_gamma() {
+    const UnitCell a(1.0, 1.0, 1.0, 90.0, 90.0, 90.0);
+    UnitCell b(a);
+    b.setgamma(100);
+    TS_ASSERT_DIFFERS(a, b);
+  }
 };
diff --git a/Framework/Kernel/inc/MantidKernel/PropertyManager.h b/Framework/Kernel/inc/MantidKernel/PropertyManager.h
index 3314b5d9253e6ef7eaac07e84d48baafcb654b64..fc0d44126012710f6badb73825cdc0c30811c150 100644
--- a/Framework/Kernel/inc/MantidKernel/PropertyManager.h
+++ b/Framework/Kernel/inc/MantidKernel/PropertyManager.h
@@ -108,6 +108,9 @@ public:
   /// Return the property manager serialized as a json object.
   ::Json::Value asJson(bool withDefaultValues = false) const override;
 
+  bool operator==(const PropertyManager &other) const;
+  bool operator!=(const PropertyManager &other) const;
+
 protected:
   friend class PropertyManagerOwner;
 
diff --git a/Framework/Kernel/src/PropertyManager.cpp b/Framework/Kernel/src/PropertyManager.cpp
index fb5da0dddb339f995d210402903af4ad0fdc0177..17a51367f32ff456ab353ab49260062566587a2f 100644
--- a/Framework/Kernel/src/PropertyManager.cpp
+++ b/Framework/Kernel/src/PropertyManager.cpp
@@ -583,6 +583,22 @@ std::string PropertyManager::asString(bool withDefaultValues) const {
   return jsonMap;
 }
 
+bool PropertyManager::operator==(const PropertyManager &other) const {
+  if (other.m_properties.size() != m_properties.size())
+    return false;
+  for (const auto &[key, value] : m_properties) {
+    if (other.m_properties.count(key) != 1)
+      return false;
+    if (*other.m_properties.at(key) != *value)
+      return false;
+  }
+  return true;
+}
+
+bool PropertyManager::operator!=(const PropertyManager &other) const {
+  return !this->operator==(other);
+}
+
 //-----------------------------------------------------------------------------------------------
 /** Get a property by name
  *  @param name :: The name of the property (case insensitive)
diff --git a/Framework/Kernel/test/PropertyManagerTest.h b/Framework/Kernel/test/PropertyManagerTest.h
index b20f36210793a57905352f382e9b71a49c2837c9..2f95d6edc2f2ccdc0444eb93d5743669a54cb525 100644
--- a/Framework/Kernel/test/PropertyManagerTest.h
+++ b/Framework/Kernel/test/PropertyManagerTest.h
@@ -659,6 +659,43 @@ public:
     TS_ASSERT_EQUALS(i, 33);
   }
 
+  void test_operator_equals_same_contents() {
+    PropertyManager a;
+    PropertyManager b;
+
+    a.declareProperty("Prop1", 10);
+    b.declareProperty("Prop1", 10);
+    TS_ASSERT_EQUALS(a, b);
+    TS_ASSERT(!(a != b));
+  }
+
+  void test_operator_not_equals_different_sizes() {
+    PropertyManager a;
+    PropertyManager b;
+
+    b.declareProperty("Prop1", 10);
+    TSM_ASSERT_DIFFERS("Unequal sizes", a, b);
+    TSM_ASSERT("Unequal sizes", !(a == b));
+  }
+
+  void test_operator_not_equals_different_keys() {
+    PropertyManager a;
+    PropertyManager b;
+    a.declareProperty("Prop1", 1);
+    b.declareProperty("Prop2", 1);
+    TSM_ASSERT_DIFFERS("Different Keys", a, b);
+    TSM_ASSERT("Different Keys", !(a == b));
+  }
+
+  void test_operator_not_equals_different_vaues() {
+    PropertyManager a;
+    PropertyManager b;
+    a.declareProperty("Prop1", 1);
+    b.declareProperty("Prop1", 2);
+    TSM_ASSERT_DIFFERS("Different Values", a, b);
+    TSM_ASSERT("Different Values", !(a == b));
+  }
+
 private:
   std::unique_ptr<PropertyManagerHelper> manager;
 };
diff --git a/Framework/PythonInterface/mantid/api/src/Exports/Run.cpp b/Framework/PythonInterface/mantid/api/src/Exports/Run.cpp
index 1a7e17e2221e264269fef95550ce39d5af8e889c..a05ffb787e3699d793277ed2d1f199dd9e399b03 100644
--- a/Framework/PythonInterface/mantid/api/src/Exports/Run.cpp
+++ b/Framework/PythonInterface/mantid/api/src/Exports/Run.cpp
@@ -243,5 +243,6 @@ void export_Run() {
       .def("__setitem__", &addOrReplaceProperty,
            (arg("self"), arg("name"), arg("value")))
       .def("__copy__", &Mantid::PythonInterface::generic__copy__<Run>)
-      .def("__deepcopy__", &Mantid::PythonInterface::generic__deepcopy__<Run>);
+      .def("__deepcopy__", &Mantid::PythonInterface::generic__deepcopy__<Run>)
+      .def("__eq__", &Run::operator==, arg("self"), arg("other"));
 }
diff --git a/Framework/PythonInterface/mantid/api/src/Exports/Sample.cpp b/Framework/PythonInterface/mantid/api/src/Exports/Sample.cpp
index c8f153e8715ca740be8dca73ea84a48e0cbba964..ffba827eca114e114acccf7424aca1d2c77eb0fc 100644
--- a/Framework/PythonInterface/mantid/api/src/Exports/Sample.cpp
+++ b/Framework/PythonInterface/mantid/api/src/Exports/Sample.cpp
@@ -87,5 +87,6 @@ void export_Sample() {
            return_internal_reference<>())
       .def("__copy__", &Mantid::PythonInterface::generic__copy__<Sample>)
       .def("__deepcopy__",
-           &Mantid::PythonInterface::generic__deepcopy__<Sample>);
+           &Mantid::PythonInterface::generic__deepcopy__<Sample>)
+      .def("__eq__", &Sample::operator==,(arg("self"), arg("other")));
 }
diff --git a/Framework/PythonInterface/test/python/mantid/api/RunTest.py b/Framework/PythonInterface/test/python/mantid/api/RunTest.py
index 1bbe8fedc23479111aca600e8b12f713f1bce65d..27c7947ad1c9c3b6cf4d3bdd1d02ef3bd70cd4c6 100644
--- a/Framework/PythonInterface/test/python/mantid/api/RunTest.py
+++ b/Framework/PythonInterface/test/python/mantid/api/RunTest.py
@@ -155,6 +155,12 @@ class RunTest(unittest.TestCase):
     def test_deep_copyable(self):
         self.do_test_copyable(copy.deepcopy)
 
+    def test_equals(self):
+        other = copy.deepcopy(self._run)
+        self.assertEqual(self._run, other)
+        other.addProperty("pressure", 1, True)
+        self.assertNotEqual(self._run, other)
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/Framework/PythonInterface/test/python/mantid/api/SampleTest.py b/Framework/PythonInterface/test/python/mantid/api/SampleTest.py
index 845075019e5fab9405f5bdd69b095495f9483a27..f3f7244902d721d2c1488bcc47e429a8e5f4eea6 100644
--- a/Framework/PythonInterface/test/python/mantid/api/SampleTest.py
+++ b/Framework/PythonInterface/test/python/mantid/api/SampleTest.py
@@ -124,8 +124,12 @@ class SampleTest(unittest.TestCase):
     def test_deep_copyable(self):
         self.do_test_copyable(copy.deepcopy)
 
-
-
+    def test_equals(self):
+        a = Sample()
+        b = Sample()
+        self.assertEqual(a, b)
+        b.setThickness(10)
+        self.assertNotEqual(a, b)
 
 if __name__ == '__main__':
     unittest.main()