diff --git a/Framework/API/inc/MantidAPI/GridDomain1D.h b/Framework/API/inc/MantidAPI/GridDomain1D.h
index 02689f95e5263c01c2a01771a7731961ba94f8fb..ca98152e2df5f5c18b7cdafe22d895fbe5ee2d89 100644
--- a/Framework/API/inc/MantidAPI/GridDomain1D.h
+++ b/Framework/API/inc/MantidAPI/GridDomain1D.h
@@ -11,7 +11,7 @@
 // Includes
 //----------------------------------------------------------------------
 #include <stdexcept>
-
+#include <string>
 #include "MantidAPI/DllConfig.h"
 #include "MantidAPI/GridDomain.h"
 
diff --git a/Framework/API/inc/MantidAPI/WorkspaceOpOverloads.h b/Framework/API/inc/MantidAPI/WorkspaceOpOverloads.h
index d3a14845ea4340ae9a46fefcd39ec2f0fd9c2658..74d8afe4599dee033c83c4750d38eb2d8a62e878 100644
--- a/Framework/API/inc/MantidAPI/WorkspaceOpOverloads.h
+++ b/Framework/API/inc/MantidAPI/WorkspaceOpOverloads.h
@@ -9,6 +9,7 @@
 
 #include "MantidAPI/DllConfig.h"
 #include "MantidAPI/MatrixWorkspace_fwd.h"
+#include <string>
 
 namespace Mantid {
 namespace API {
diff --git a/Framework/Kernel/inc/MantidKernel/Material.h b/Framework/Kernel/inc/MantidKernel/Material.h
index fce89375ee03eab8545e200f357e8549ab13821a..c5088567ec8baa4155d4b74e8bd628bf1b9ae93e 100644
--- a/Framework/Kernel/inc/MantidKernel/Material.h
+++ b/Framework/Kernel/inc/MantidKernel/Material.h
@@ -14,6 +14,7 @@
 #include "MantidKernel/PhysicalConstants.h"
 #include <boost/shared_ptr.hpp>
 #include <vector>
+#include <string>
 
 // Forward Declares
 namespace NeXus {
diff --git a/Framework/NexusGeometry/src/NexusGeometryParser.cpp b/Framework/NexusGeometry/src/NexusGeometryParser.cpp
index 2b78be367c5506e489eb0dcb26fa4c61bd8b4d37..cba48969581d16dac88b4a10c1f5fc40ce3fa423 100644
--- a/Framework/NexusGeometry/src/NexusGeometryParser.cpp
+++ b/Framework/NexusGeometry/src/NexusGeometryParser.cpp
@@ -531,61 +531,35 @@ private:
                      const std::vector<float> &vertices,
                      const std::unordered_map<int, uint32_t> &detIdToIndex,
                      const std::vector<uint32_t> &faceIndices,
-                     const size_t vertsPerFace,
                      std::vector<std::vector<Eigen::Vector3d>> &detFaceVerts,
                      std::vector<std::vector<uint32_t>> &detFaceIndices,
                      std::vector<std::vector<uint32_t>> &detWindingOrder,
                      std::vector<int32_t> &detIds) {
-    const size_t vertStride = 3;
-    std::fill(detFaceIndices.begin(), detFaceIndices.end(),
-              std::vector<uint32_t>(1, 0));
     for (size_t i = 0; i < detFaces.size(); i += 2) {
-      const auto faceIndex = faceIndices[detFaces[i]];
+      const auto faceIndexOfDetector = detFaces[i];
+      const auto faceIndex = faceIndices[faceIndexOfDetector];
+      auto nextFaceIndex = windingOrder.size();
+      if (faceIndexOfDetector + 1 < detFaces.size())
+        nextFaceIndex = faceIndices[faceIndexOfDetector + 1];
+      const auto nVertsInFace = nextFaceIndex - faceIndex;
       const auto detID = detFaces[i + 1];
       const auto detIndex = detIdToIndex.at(detID);
       auto &vertsForDet = detFaceVerts[detIndex];
       auto &detWinding = detWindingOrder[detIndex];
-      auto &detIndices = detFaceIndices[detIndex];
-      vertsForDet.reserve(vertsPerFace);
-      detWinding.reserve(vertsPerFace);
-      // Use face index to index into winding order.
-      for (size_t j = faceIndex; j < vertsPerFace + faceIndex; ++j) {
-        for (size_t v = 0; v < vertsPerFace; ++v) {
-          const auto vi = windingOrder[faceIndex + v] * vertStride;
-          vertsForDet.emplace_back(vertices[vi], vertices[vi + 1],
-                                   vertices[vi + 2]);
-          detWinding.push_back(static_cast<uint32_t>(detWinding.size()));
-        }
+      vertsForDet.reserve(nVertsInFace);
+      detWinding.reserve(nVertsInFace);
+      detFaceIndices[detIndex].push_back(
+          faceIndex); // Associate face with detector index
+                      // Use face index to index into winding order.
+      for (size_t v = 0; v < nVertsInFace; ++v) {
+        const auto vi = windingOrder[faceIndex + v] * 3;
+        vertsForDet.emplace_back(vertices[vi], vertices[vi + 1],
+                                 vertices[vi + 2]);
+        detWinding.push_back(static_cast<uint32_t>(detWinding.size()));
       }
-
       // Index -> Id
       detIds[detIndex] = detID;
-      detIndices.push_back(static_cast<uint32_t>(vertsForDet.size()));
     }
-    /*
-        std::fill(detFaceIndices.begin(), detFaceIndices.end(),
-                  std::vector<uint32_t>(1, 0));
-        for (size_t i = 0; i < windingOrder.size(); i += vertsPerFace) {
-          auto detFaceId = detFaces[detFaceIndex];
-          // Id -> Index
-          auto detIndex = detIdToIndex.at(detFaceId);
-          auto &detVerts = detFaceVerts[detIndex];
-          auto &detIndices = detFaceIndices[detIndex];
-          auto &detWinding = detWindingOrder[detIndex];
-          detVerts.reserve(vertsPerFace);
-          detWinding.reserve(vertsPerFace);
-          for (size_t v = 0; v < vertsPerFace; ++v) {
-            const auto vi = windingOrder[i + v] * vertStride;
-            detVerts.emplace_back(vertices[vi], vertices[vi + 1], vertices[vi +
-       2]); detWinding.push_back(static_cast<uint32_t>(detWinding.size()));
-          }
-          // Index -> Id
-          detIds[detIndex] = detFaceId;
-          detIndices.push_back(static_cast<uint32_t>(detVerts.size()));
-          // Detector faces is 2N detectors
-          detFaceIndex += 2;
-        }
-        */
   }
 
   void parseNexusMeshAndAddDetectors(
@@ -595,30 +569,30 @@ private:
       const std::vector<float> &vertices, const size_t numDets,
       const std::unordered_map<int, uint32_t> &detIdToIndex,
       const std::string &name, InstrumentBuilder &builder) {
-    auto vertsPerFace = windingOrder.size() / faceIndices.size();
     std::vector<std::vector<Eigen::Vector3d>> detFaceVerts(numDets);
     std::vector<std::vector<uint32_t>> detFaceIndices(numDets);
     std::vector<std::vector<uint32_t>> detWindingOrder(numDets);
     std::vector<int> detIds(numDets);
 
     extractFacesAndIDs(detFaces, windingOrder, vertices, detIdToIndex,
-                       faceIndices, vertsPerFace, detFaceVerts, detFaceIndices,
+                       faceIndices, detFaceVerts, detFaceIndices,
                        detWindingOrder, detIds);
 
     for (size_t i = 0; i < numDets; ++i) {
       auto &detVerts = detFaceVerts[i];
-      const auto &detIndices = detFaceIndices[i];
+      const auto &faceIndices = detFaceIndices[i];
       const auto &detWinding = detWindingOrder[i];
       // Calculate polygon centre
-      auto centre = std::accumulate(detVerts.begin() + 1, detVerts.end(),
-                                    detVerts.front()) /
-                    detVerts.size();
+      Eigen::Vector3d centre =
+          std::accumulate(detVerts.begin() + 1, detVerts.end(),
+                          detVerts.front()) /
+          detVerts.size();
 
       // translate shape to origin for shape coordinates.
       std::for_each(detVerts.begin(), detVerts.end(),
                     [&centre](Eigen::Vector3d &val) { val -= centre; });
 
-      auto shape = NexusShapeFactory::createFromOFFMesh(detIndices, detWinding,
+      auto shape = NexusShapeFactory::createFromOFFMesh(faceIndices, detWinding,
                                                         detVerts);
       builder.addDetectorToLastBank(name + "_" + std::to_string(i), detIds[i],
                                     centre, std::move(shape));
diff --git a/Framework/NexusGeometry/src/NexusGeometrySave.cpp.orig b/Framework/NexusGeometry/src/NexusGeometrySave.cpp.orig
new file mode 100644
index 0000000000000000000000000000000000000000..544d2290a25741af45b721bff1a2514571b4d027
--- /dev/null
+++ b/Framework/NexusGeometry/src/NexusGeometrySave.cpp.orig
@@ -0,0 +1,1212 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+
+/*
+ * NexusGeometrySave::saveInstrument :
+ * Save methods to save geometry and metadata from memory
+ * to disk in Nexus file format for Instrument 2.0.
+ *
+ * @author Takudzwa Makoni, RAL (UKRI), ISIS
+ * @date 07/08/2019
+ */
+
+#include "MantidNexusGeometry/NexusGeometrySave.h"
+#include "MantidAPI/SpectraDetectorTypes.h"
+#include "MantidAPI/SpectrumInfo.h"
+#include "MantidGeometry/Instrument/ComponentInfo.h"
+#include "MantidGeometry/Instrument/ComponentInfoBankHelpers.h"
+#include "MantidGeometry/Instrument/DetectorInfo.h"
+#include "MantidIndexing/IndexInfo.h"
+#include "MantidKernel/EigenConversionHelpers.h"
+#include "MantidKernel/ProgressBase.h"
+#include "MantidNexusGeometry/H5ForwardCompatibility.h"
+#include "MantidNexusGeometry/NexusGeometryDefinitions.h"
+#include "MantidNexusGeometry/NexusGeometryUtilities.h"
+#include <H5Cpp.h>
+#include <algorithm>
+#include <boost/filesystem/operations.hpp>
+#include <cmath>
+#include <list>
+#include <memory>
+#include <regex>
+#include <string>
+
+namespace Mantid {
+namespace NexusGeometry {
+namespace NexusGeometrySave {
+using namespace Geometry::ComponentInfoBankHelpers;
+/*
+ * Helper container for spectrum mapping information info
+ */
+struct SpectraMappings {
+  std::vector<int32_t> detector_index;
+  std::vector<int32_t> detector_count;
+  std::vector<int32_t> detector_list;
+  std::vector<int32_t> spectra_ids;
+  size_t number_spec = 0;
+  size_t number_dets = 0;
+};
+
+/** Function tryCreatGroup. will try to create a new child group with the given
+ * name inside the parent group. if a child group with that name already exists
+ * in the parent group, throws std::invalid_argument. H5 will not allow us to
+ * save duplicate groups with the same name, so this provides a utility for an
+ * eager check.
+ *
+ * @param parentGroup : H5 parent group.
+ * @param childGroupName : intended name of the child goup.
+ * @return : new H5 Group object with name <childGroupName> if did not throw.
+ */
+inline H5::Group tryCreateGroup(const H5::Group &parentGroup,
+                                const std::string &childGroupName) {
+  H5std_string parentGroupName = H5_OBJ_NAME(parentGroup);
+  for (hsize_t i = 0; i < parentGroup.getNumObjs(); ++i) {
+    if (parentGroup.getObjTypeByIdx(i) == GROUP_TYPE) {
+      H5std_string child = parentGroup.getObjnameByIdx(i);
+      if (childGroupName == child) {
+        // TODO: runtime error instead?
+        throw std::invalid_argument(
+            "Cannot create group with name " + childGroupName +
+            " inside parent group " + parentGroupName +
+            " because a group with this name already exists.");
+      }
+    }
+  }
+  return parentGroup.createGroup(childGroupName);
+}
+/*
+ * Function toStdVector (Overloaded). Store data in Mantid::Kernel::V3D vector
+ * into std::vector<double> vector. Used by saveInstrument to write array-type
+ * datasets to file.
+ *
+ * @param data : Mantid::Kernel::V3D vector containing data values
+ * @return std::vector<double> vector containing data values in
+ * Mantid::Kernel::V3D format.
+ */
+std::vector<double> toStdVector(const V3D &data) {
+  std::vector<double> stdVector;
+  stdVector.reserve(3);
+  stdVector.push_back(data.X());
+  stdVector.push_back(data.Y());
+  stdVector.push_back(data.Z());
+  return stdVector;
+}
+
+/*
+ * Function toStdVector (Overloaded). Store data in Eigen::Vector3d vector
+ * into std::vector<double> vector. Used by saveInstrument to write array-type
+ * datasets to file.
+ *
+ * @param data : Eigen::Vector3d vector containing data values
+ * @return std::vector<double> vector containing data values in
+ * Eigen::Vector3d format
+ */
+std::vector<double> toStdVector(const Eigen::Vector3d &data) {
+  return toStdVector(Kernel::toV3D(data));
+}
+
+/*
+ * Function: isApproxZero. returns true if all values in an variable-sized
+ * std-vector container evaluate to zero with a given level of precision. Used
+ * by SaveInstrument methods to determine whether or not to write a dataset to
+ * file.
+ *
+ * @param data : std::vector<T> data
+ * @param precision : double precision specifier
+ * @return true if all elements are approx zero, else false.
+ */
+bool isApproxZero(const std::vector<double> &data, const double &precision) {
+
+  return std::all_of(data.begin(), data.end(),
+                     [&precision](const double &element) {
+                       return std::abs(element) < precision;
+                     });
+}
+
+// overload. return true if vector is approx to zero
+bool isApproxZero(const Eigen::Vector3d &data, const double &precision) {
+  return data.isApprox(Eigen::Vector3d(0, 0, 0), precision);
+}
+
+// overload. returns true is angle is approx to zero
+bool isApproxZero(const Eigen::Quaterniond &data, const double &precision) {
+  return data.isApprox(Eigen::Quaterniond(1, 0, 0, 0), precision);
+}
+
+/*
+ * Function: strTypeOfSize
+ * Produces the HDF StrType of size equal to that of the
+ * input string.
+ *
+ * @param str : std::string
+ * @return string datatype of size = length of input string
+ */
+H5::StrType strTypeOfSize(const std::string &str) {
+  H5::StrType stringType(H5::PredType::C_S1, str.size());
+  return stringType;
+}
+
+/*
+ * Function: writeStrDataset
+ * writes a StrType HDF dataset and dataset value to a HDF group.
+ *
+ * @param grp : HDF group object.
+ * @param attrname : attribute name.
+ * @param attrVal : string attribute value to be stored in attribute.
+ */
+void writeStrDataset(H5::Group &grp, const std::string &dSetName,
+                     const std::string &dSetVal,
+                     const H5::DataSpace &dataSpace = SCALAR) {
+  // TODO. may need to review if we shoud in fact replace.
+  if (!utilities::findDataset(grp, dSetName)) {
+    H5::StrType dataType = strTypeOfSize(dSetVal);
+    H5::DataSet dSet = grp.createDataSet(dSetName, dataType, dataSpace);
+    dSet.write(dSetVal, dataType);
+  }
+}
+
+/*
+ * Function: writeStrAttribute
+ * writes a StrType HDF attribute and attribute value to a HDF group.
+ *
+ * @param grp : HDF group object.
+ * @param attrname : attribute name.
+ * @param attrVal : string attribute value to be stored in attribute.
+ */
+void writeStrAttribute(H5::Group &grp, const std::string &attrName,
+                       const std::string &attrVal,
+                       const H5::DataSpace &dataSpace = SCALAR) {
+  if (!grp.attrExists(attrName)) {
+    H5::StrType dataType = strTypeOfSize(attrVal);
+    H5::Attribute attribute =
+        grp.createAttribute(attrName, dataType, dataSpace);
+    attribute.write(dataType, attrVal);
+  }
+}
+
+/*
+ * Function: writeStrAttribute
+ * Overload function which writes a StrType HDF attribute and attribute value
+ * to a HDF dataset.
+ *
+ * @param dSet : HDF dataset object.
+ * @param attrname : attribute name.
+ * @param attrVal : string attribute value to be stored in attribute.
+ */
+void writeStrAttribute(H5::DataSet &dSet, const std::string &attrName,
+                       const std::string &attrVal,
+                       const H5::DataSpace &dataSpace = SCALAR) {
+  if (!dSet.attrExists(attrName)) {
+    H5::StrType dataType = strTypeOfSize(attrVal);
+    auto attribute = dSet.createAttribute(attrName, dataType, dataSpace);
+    attribute.write(dataType, attrVal);
+  }
+}
+
+/*
+ * Function: writeXYZPixeloffset
+ * write the x, y, and z offset of the pixels from the parent detector bank as
+ * HDF5 datasets to HDF5 group. If all of the pixel offsets in either x, y, or z
+ * are approximately zero, skips writing that dataset to file.
+ * @param grp : HDF5 parent group
+ * @param compInfo : Component Info Instrument cache
+ * @param idx : index of bank in cache.
+ */
+void writeXYZPixeloffset(H5::Group &grp,
+                         const Geometry::ComponentInfo &compInfo,
+                         const size_t idx) {
+
+  H5::DataSet xPixelOffset, yPixelOffset, zPixelOffset;
+  auto childrenDetectors = compInfo.detectorsInSubtree(idx);
+
+  std::vector<double> posx;
+  std::vector<double> posy;
+  std::vector<double> posz;
+
+  posx.reserve(childrenDetectors.size());
+  posy.reserve(childrenDetectors.size());
+  posz.reserve(childrenDetectors.size());
+
+  for (const size_t &i : childrenDetectors) {
+
+    auto offset = Geometry::ComponentInfoBankHelpers::offsetFromAncestor(
+        compInfo, idx, i);
+
+    posx.push_back(offset[0]);
+    posy.push_back(offset[1]);
+    posz.push_back(offset[2]);
+  }
+
+  bool xIsZero = isApproxZero(posx, PRECISION);
+  bool yIsZero = isApproxZero(posy, PRECISION);
+  bool zIsZero = isApproxZero(posz, PRECISION);
+
+  auto bankName = compInfo.name(idx);
+  const auto nDetectorsInBank = static_cast<hsize_t>(posx.size());
+
+  int rank = 1;
+  hsize_t dims[static_cast<hsize_t>(1)];
+  dims[0] = nDetectorsInBank;
+
+  H5::DataSpace space = H5Screate_simple(rank, dims, nullptr);
+
+  if (!xIsZero) {
+    xPixelOffset =
+        grp.createDataSet(X_PIXEL_OFFSET, H5::PredType::NATIVE_DOUBLE, space);
+    xPixelOffset.write(posx.data(), H5::PredType::NATIVE_DOUBLE, space);
+    writeStrAttribute(xPixelOffset, UNITS, METRES);
+  }
+
+  if (!yIsZero) {
+    yPixelOffset =
+        grp.createDataSet(Y_PIXEL_OFFSET, H5::PredType::NATIVE_DOUBLE, space);
+    yPixelOffset.write(posy.data(), H5::PredType::NATIVE_DOUBLE);
+    writeStrAttribute(yPixelOffset, UNITS, METRES);
+  }
+
+  if (!zIsZero) {
+    zPixelOffset =
+        grp.createDataSet(Z_PIXEL_OFFSET, H5::PredType::NATIVE_DOUBLE, space);
+    zPixelOffset.write(posz.data(), H5::PredType::NATIVE_DOUBLE);
+    writeStrAttribute(zPixelOffset, UNITS, METRES);
+  }
+}
+
+template <typename T>
+void write1DIntDataset(H5::Group &grp, const H5std_string &name,
+                       const std::vector<T> &container) {
+  const int rank = 1;
+  hsize_t dims[1] = {static_cast<hsize_t>(container.size())};
+
+  H5::DataSpace space = H5Screate_simple(rank, dims, nullptr);
+
+  auto dataset = grp.createDataSet(name, H5::PredType::NATIVE_INT, space);
+  if (!container.empty())
+    dataset.write(container.data(), H5::PredType::NATIVE_INT, space);
+}
+
+/*
+ * Function: writeNXDetectorNumber
+ * For use with NXdetector group. Writes the detector numbers for all detector
+ * pixels in compInfo to a new dataset in the group.
+ *
+ * @param detectorIDs : std::vector<int> container of all detectorIDs to be
+ * stored into dataset 'detector_number'.
+ * @param compInfo : instrument cache with component info.
+ * @idx : size_t index of bank in compInfo.
+ */
+void writeNXDetectorNumber(H5::Group &grp,
+                           const Geometry::ComponentInfo &compInfo,
+                           const std::vector<int> &detectorIDs,
+                           const size_t idx) {
+
+  H5::DataSet detectorNumber;
+
+  std::vector<int> bankDetIDs; // IDs of detectors beloning to bank
+  std::vector<size_t> bankDetectors =
+      compInfo.detectorsInSubtree(idx); // Indexes of children detectors in bank
+  bankDetIDs.reserve(bankDetectors.size());
+
+  // write the ID for each child detector to std::vector to be written to
+  // dataset
+  std::for_each(bankDetectors.begin(), bankDetectors.end(),
+                [&bankDetIDs, &detectorIDs](const size_t index) {
+                  bankDetIDs.push_back(detectorIDs[index]);
+                });
+
+  write1DIntDataset(grp, DETECTOR_IDS, bankDetIDs);
+}
+
+// Write the count of how many detectors contribute to each spectra
+void writeDetectorCount(H5::Group &grp, const SpectraMappings &mappings) {
+  write1DIntDataset(grp, SPECTRA_COUNTS, mappings.detector_count);
+}
+
+// Write the detectors ids ordered by spectra index 0 - N for each NXDetector
+void writeDetectorList(H5::Group &grp, const SpectraMappings &mappings) {
+  write1DIntDataset(grp, DETECTOR_LIST, mappings.detector_list);
+}
+
+// Write the detector indexes. This provides offsets into the detector_list and
+// is sized to the number of spectra
+void writeDetectorIndex(H5::Group &grp, const SpectraMappings &mappings) {
+  write1DIntDataset(grp, DETECTOR_INDEX, mappings.detector_index);
+}
+
+// Write the spectra numbers for each spectra
+void writeSpectra(H5::Group &grp, const SpectraMappings &mappings) {
+  write1DIntDataset(grp, SPECTRA_NUMBERS, mappings.spectra_ids);
+}
+
+/*
+ * Function: writeNXMonitorNumber
+ * For use with NXmonitor group. write 'detector_id's of an NXmonitor, which
+ * is a specific type of pixel, to its group.
+ *
+ * @param grp : NXmonitor group (HDF group)
+ * @param monitorID : monitor ID to be
+ * stored into dataset 'detector_id' (or 'detector_number'. naming convention
+ * inconsistency?).
+ */
+void writeNXMonitorNumber(H5::Group &grp, const int monitorID) {
+
+  // these DataSets are duplicates of each other. written to the NXmonitor
+  // group to handle the naming inconsistency. probably temporary.
+  H5::DataSet detectorNumber, detector_id;
+
+  int rank = 1;
+  hsize_t dims[static_cast<hsize_t>(1)];
+  dims[0] = static_cast<hsize_t>(1);
+
+  H5::DataSpace space = H5Screate_simple(rank, dims, nullptr);
+
+  // these DataSets are duplicates of each other. written to the group to
+  // handle the naming inconsistency. probably temporary.
+  if (!utilities::findDataset(grp, DETECTOR_IDS)) {
+    detectorNumber =
+        grp.createDataSet(DETECTOR_IDS, H5::PredType::NATIVE_INT, space);
+    detectorNumber.write(&monitorID, H5::PredType::NATIVE_INT, space);
+  }
+  if (!utilities::findDataset(grp, DETECTOR_ID)) {
+
+    detector_id =
+        grp.createDataSet(DETECTOR_ID, H5::PredType::NATIVE_INT, space);
+    detector_id.write(&monitorID, H5::PredType::NATIVE_INT, space);
+  }
+}
+
+/*
+ * Function: writeLocation
+ * For use with NXdetector group. Writes absolute position of detector bank to
+ * dataset and metadata as attributes.
+ *
+ * @param grp : NXdetector group : (HDF group)
+ * @param position : Eigen::Vector3d position of component in instrument cache.
+ */
+inline void writeLocation(H5::Group &grp, const Eigen::Vector3d &position) {
+
+  std::string dependency = NO_DEPENDENCY; // self dependent
+
+  double norm;
+
+  H5::DataSet location;
+  H5::DataSpace dspace;
+  H5::DataSpace aspace;
+
+  H5::Attribute vector;
+  H5::Attribute units;
+  H5::Attribute transformationType;
+  H5::Attribute dependsOn;
+
+  H5::StrType strSize;
+
+  int drank = 1;                          // rank of dataset
+  hsize_t ddims[static_cast<hsize_t>(1)]; // dimensions of dataset
+  ddims[0] = static_cast<hsize_t>(1);     // datapoints in dataset dimension 0
+
+  norm = position.norm();               // norm od the position vector
+  auto unitVec = position.normalized(); // unit vector of the position vector
+  std::vector<double> stdNormPos =
+      toStdVector(unitVec); // convert to std::vector
+
+  dspace = H5Screate_simple(drank, ddims, nullptr); // dataspace for dataset
+  location = grp.createDataSet(LOCATION, H5::PredType::NATIVE_DOUBLE,
+                               dspace); // dataset location
+  location.write(&norm, H5::PredType::NATIVE_DOUBLE,
+                 dspace); // write norm to location
+
+  int arank = 1;                          // rank of attribute
+  hsize_t adims[static_cast<hsize_t>(3)]; // dimensions of attribute
+  adims[0] = 3;                           // datapoints in attribute dimension 0
+
+  aspace = H5Screate_simple(arank, adims, nullptr); // dataspace for attribute
+  vector = location.createAttribute(VECTOR, H5::PredType::NATIVE_DOUBLE,
+                                    aspace); // attribute vector
+  vector.write(H5::PredType::NATIVE_DOUBLE,
+               stdNormPos.data()); // write unit vector to vector
+
+  // units attribute
+  strSize = strTypeOfSize(METRES);
+  units = location.createAttribute(UNITS, strSize, SCALAR);
+  units.write(strSize, METRES);
+
+  // transformation-type attribute
+  strSize = strTypeOfSize(TRANSLATION);
+  transformationType =
+      location.createAttribute(TRANSFORMATION_TYPE, strSize, SCALAR);
+  transformationType.write(strSize, TRANSLATION);
+
+  // dependency attribute
+  strSize = strTypeOfSize(dependency);
+  dependsOn = location.createAttribute(DEPENDS_ON, strSize, SCALAR);
+  dependsOn.write(strSize, dependency);
+}
+
+/*
+ * Function: writeOrientation
+ * For use with NXdetector group. Writes the absolute rotation of detector
+ * bank to dataset and metadata as attributes.
+ *
+ * @param grp : NXdetector group : (HDF group)
+ * @param rotation : Eigen::Quaterniond rotation of component in instrument
+ * cache.
+ * @param dependency : dependency of the orientation dataset:
+ * Compliant to the Mantid Instrument Definition file, if a translation
+ * exists, it precedes a rotation.
+ * https://docs.mantidproject.org/nightly/concepts/InstrumentDefinitionFile.html
+ */
+inline void writeOrientation(H5::Group &grp, const Eigen::Quaterniond &rotation,
+                             const std::string &dependency) {
+
+  // dependency for orientation defaults to self-dependent. If Location
+  // dataset exists, the orientation will depend on it instead.
+
+  double angle;
+
+  H5::DataSet orientation;
+  H5::DataSpace dspace;
+  H5::DataSpace aspace;
+
+  H5::Attribute vector;
+  H5::Attribute units;
+  H5::Attribute transformationType;
+  H5::Attribute dependsOn;
+
+  H5::StrType strSize;
+
+  int drank = 1;                          // rank of dataset
+  hsize_t ddims[static_cast<hsize_t>(1)]; // dimensions of dataset
+  ddims[0] = static_cast<hsize_t>(1);     // datapoints in dataset dimension 0
+
+  angle = std::acos(rotation.w()) * (360.0 / M_PI); // angle magnitude
+  Eigen::Vector3d axisOfRotation = rotation.vec().normalized(); // angle axis
+  std::vector<double> stdNormAxis =
+      toStdVector(axisOfRotation); // convert to std::vector
+
+  dspace = H5Screate_simple(drank, ddims, nullptr); // dataspace for dataset
+  orientation = grp.createDataSet(ORIENTATION, H5::PredType::NATIVE_DOUBLE,
+                                  dspace); // dataset orientation
+  orientation.write(&angle, H5::PredType::NATIVE_DOUBLE,
+                    dspace); // write angle magnitude to orientation
+
+  int arank = 1;                          // rank of attribute
+  hsize_t adims[static_cast<hsize_t>(3)]; // dimensions of attribute
+  adims[0] = static_cast<hsize_t>(3);     // datapoints in attibute dimension 0
+
+  aspace = H5Screate_simple(arank, adims, nullptr); // dataspace for attribute
+  vector = orientation.createAttribute(VECTOR, H5::PredType::NATIVE_DOUBLE,
+                                       aspace); // attribute vector
+  vector.write(H5::PredType::NATIVE_DOUBLE,
+               stdNormAxis.data()); // write angle axis to vector
+
+  // units attribute
+  strSize = strTypeOfSize(DEGREES);
+  units = orientation.createAttribute(UNITS, strSize, SCALAR);
+  units.write(strSize, DEGREES);
+
+  // transformation-type attribute
+  strSize = strTypeOfSize(ROTATION);
+  transformationType =
+      orientation.createAttribute(TRANSFORMATION_TYPE, strSize, SCALAR);
+  transformationType.write(strSize, ROTATION);
+
+  // dependency attribute
+  strSize = strTypeOfSize(dependency);
+  dependsOn = orientation.createAttribute(DEPENDS_ON, strSize, SCALAR);
+  dependsOn.write(strSize, dependency);
+}
+
+SpectraMappings makeMappings(const Geometry::ComponentInfo &compInfo,
+                             const detid2index_map &detToIndexMap,
+                             const Indexing::IndexInfo &indexInfo,
+                             const API::SpectrumInfo &specInfo,
+                             const std::vector<Mantid::detid_t> &detIds,
+                             size_t index) {
+  auto childrenDetectors = compInfo.detectorsInSubtree(index);
+  size_t nChildDetectors =
+      childrenDetectors.size(); // Number of detectors actually considered in
+                                // spectra-detector map for this NXdetector
+  // local to this nxdetector
+  std::map<size_t, int> detector_count_map;
+  // We start knowing only the detector index, we have to establish spectra from
+  // that.
+  for (const auto det_index : childrenDetectors) {
+    auto detector_id = detIds[det_index];
+
+    // A detector might not belong to any spectrum at all.
+    if (detToIndexMap.find(detector_id) != detToIndexMap.end()) {
+      auto spectrum_index = detToIndexMap.at(detector_id);
+      detector_count_map[spectrum_index]++; // Attribute detector to a give
+                                            // spectrum_index
+    } else {
+      --nChildDetectors; // Detector is not part of any spectra-detector
+                         // mapping. So we have one less detector to consider
+                         // recording
+    }
+  }
+  // Sized to spectra in bank
+  SpectraMappings mappings;
+  mappings.detector_list.resize(nChildDetectors);
+  mappings.detector_count.resize(detector_count_map.size(), 0);
+  mappings.detector_index.resize(detector_count_map.size() + 1, 0);
+  mappings.spectra_ids.resize(detector_count_map.size(), 0);
+  mappings.number_dets = nChildDetectors;
+  mappings.number_spec = detector_count_map.size();
+  size_t specCounter = 0;
+  size_t detCounter = 0;
+  for (auto &pair : detector_count_map) {
+    // using sort order of map to ensure we are ordered by lowest to highest
+    // spectrum index
+    mappings.detector_count[specCounter] = (pair.second); // Counts
+    mappings.detector_index[specCounter + 1] =
+        mappings.detector_index[specCounter] + (pair.second);
+    mappings.spectra_ids[specCounter] =
+        int32_t(indexInfo.spectrumNumber(pair.first));
+
+    // We will list everything by spectrum index, so we need to add the detector
+    // ids in the same order.
+    const auto &specDefintion = specInfo.spectrumDefinition(pair.first);
+    for (const auto &def : specDefintion) {
+      mappings.detector_list[detCounter] = detIds[def.first];
+      ++detCounter;
+    }
+    ++specCounter;
+  }
+  mappings.detector_index.resize(
+      detector_count_map.size()); // cut-off last item
+
+  return mappings;
+}
+
+void validateInputs(AbstractLogger &logger, const std::string &fullPath,
+                    const Geometry::ComponentInfo &compInfo) {
+  boost::filesystem::path tmp(fullPath);
+  if (!boost::filesystem::is_directory(tmp.root_directory())) {
+    throw std::invalid_argument(
+        "The path provided for saving the file is invalid: " + fullPath + "\n");
+  }
+
+  // check the file extension matches any of the valid extensions defined in
+  // nexus_geometry_extensions
+  const auto ext = boost::filesystem::path(tmp).extension();
+  bool isValidExt = std::any_of(
+      nexus_geometry_extensions.begin(), nexus_geometry_extensions.end(),
+      [&ext](const std::string &str) { return ext.generic_string() == str; });
+
+  // throw if the file extension is invalid
+  if (!isValidExt) {
+    // string of valid extensions to output in exception
+    std::string extensions;
+    std::for_each(
+        nexus_geometry_extensions.begin(), nexus_geometry_extensions.end(),
+        [&extensions](const std::string &str) { extensions += " " + str; });
+    std::string message = "invalid extension for file: '" +
+                          ext.generic_string() +
+                          "'. Expected any of: " + extensions;
+    logger.error(message);
+    throw std::invalid_argument(message);
+  }
+
+  if (!compInfo.hasDetectorInfo()) {
+    logger.error(
+        "No detector info was found in the Instrument. Instrument not saved.");
+    throw std::invalid_argument("No detector info was found in the Instrument");
+  }
+  if (!compInfo.hasSample()) {
+    logger.error(
+        "No sample was found in the Instrument. Instrument not saved.");
+    throw std::invalid_argument("No sample was found in the Instrument");
+  }
+
+  if (Mantid::Kernel::V3D{0, 0, 0} != compInfo.samplePosition()) {
+    logger.error("The sample positon is required to be at the origin. "
+                 "Instrument not saved.");
+    throw std::invalid_argument(
+        "The sample positon is required to be at the origin");
+  }
+
+  if (!compInfo.hasSource()) {
+    logger.error("No source was found in the Instrument. "
+                 "Instrument not saved.");
+    throw std::invalid_argument("No source was found in the Instrument");
+  }
+}
+
+/*
+ * Function determines if a given index has an ancestor index in the
+ * saved_indices list. This allows us to prevent duplicate saving of things that
+ * could be considered NXDetectors
+ */
+template <typename T>
+bool isDesiredNXDetector(size_t index, const T &saved_indices,
+                         const Geometry::ComponentInfo &compInfo) {
+  return saved_indices.end() ==
+         std::find_if(saved_indices.begin(), saved_indices.end(),
+                      [&compInfo, &index](const size_t idx) {
+                        return isAncestorOf(compInfo, idx, index);
+                      });
+}
+
+/**
+ * Internal save implementation. We can either write a new file containing only
+ * the geometry, or we might also need to append/merge with an existing file.
+ * Knowing the logic for this is important so we build an object around the Mode
+ * state.
+ */
+class NexusGeometrySaveImpl {
+public:
+  enum class Mode { Trunc, Append };
+
+  explicit NexusGeometrySaveImpl(Mode mode) : m_mode(mode) {}
+  NexusGeometrySaveImpl(const NexusGeometrySaveImpl &) =
+      delete; // No intention to suport copies
+
+  /*
+   * Function: NXInstrument
+   * for NXentry parent (root group). Produces an NXinstrument group in the
+   * parent group, and writes Nexus compliant datasets and metadata stored in
+   * attributes to the new group.
+   *
+   * @param parent : parent group in which to write the NXinstrument group.
+   * @param compInfo : componentinfo
+   * @return NXinstrument group, to be passed into children save methods.
+   */
+  H5::Group instrument(const H5::Group &parent,
+                       const Geometry::ComponentInfo &compInfo) {
+
+    std::string nameInCache = compInfo.name(compInfo.root());
+    std::string instrName =
+        nameInCache.empty() ? "unspecified_instrument" : nameInCache;
+
+    H5::Group childGroup = openOrCreateGroup(parent, instrName, NX_INSTRUMENT);
+
+    writeStrDataset(childGroup, NAME, instrName);
+    writeStrAttribute(childGroup, NX_CLASS, NX_INSTRUMENT);
+
+    std::string defaultShortName = instrName.substr(0, 3);
+    H5::DataSet name = childGroup.openDataSet(NAME);
+    writeStrAttribute(name, SHORT_NAME, defaultShortName);
+    return childGroup;
+  }
+
+  /*
+   * Function: saveNXSample
+   * For NXentry parent (root group). Produces an NXsample group in the parent
+   * group, and writes the Nexus compliant datasets and metadata stored in
+   * attributes to the new group.
+   *
+   * @param parent : parent group in which to write the NXinstrument group.
+   * @param compInfo : componentInfo object.
+   */
+  void sample(const H5::Group &parentGroup,
+              const Geometry::ComponentInfo &compInfo) {
+
+    std::string nameInCache = compInfo.name(compInfo.sample());
+    std::string sampleName =
+        nameInCache.empty() ? "unspecified_sample" : nameInCache;
+
+    H5::Group childGroup =
+        openOrCreateGroup(parentGroup, sampleName, NX_SAMPLE);
+    writeStrAttribute(childGroup, NX_CLASS, NX_SAMPLE);
+    writeStrDataset(childGroup, NAME, sampleName);
+  }
+
+  /*
+   * Function: saveNXSource
+   * For NXentry (root group). Produces an NXsource group in the parent group,
+   * and writes the Nexus compliant datasets and metadata stored in attributes
+   * to the new group.
+   *
+   * @param parent : parent group in which to write the NXinstrument group.
+   * @param compInfo : componentInfo object.
+   */
+  void source(const H5::Group &parentGroup,
+              const Geometry::ComponentInfo &compInfo) {
+
+    size_t index = compInfo.source();
+
+    std::string nameInCache = compInfo.name(index);
+    std::string sourceName =
+        nameInCache.empty() ? "unspecified_source" : nameInCache;
+
+    std::string dependency = NO_DEPENDENCY;
+
+    Eigen::Vector3d position =
+        Mantid::Kernel::toVector3d(compInfo.position(index));
+    Eigen::Quaterniond rotation =
+        Mantid::Kernel::toQuaterniond(compInfo.rotation(index));
+
+    bool locationIsOrigin = isApproxZero(position, PRECISION);
+    bool orientationIsZero = isApproxZero(rotation, PRECISION);
+
+    H5::Group childGroup =
+        openOrCreateGroup(parentGroup, sourceName, NX_SOURCE);
+    writeStrAttribute(childGroup, NX_CLASS, NX_SOURCE);
+
+    // do not write NXtransformations if there is no translation or rotation
+    if (!(locationIsOrigin && orientationIsZero)) {
+      H5::Group transformations =
+          simpleNXSubGroup(childGroup, TRANSFORMATIONS, NX_TRANSFORMATIONS);
+
+      // self, ".", is the default first NXsource dependency in the chain.
+      // first check translation in NXsource is non-zero, and set dependency
+      // to location if true and write location. Then check if orientation in
+      // NXsource is non-zero, replace dependency with orientation if true. If
+      // neither orientation nor location are non-zero, NXsource is self
+      // dependent.
+      if (!locationIsOrigin) {
+        dependency = H5_OBJ_NAME(transformations) + "/" + LOCATION;
+        writeLocation(transformations, position);
+      }
+      if (!orientationIsZero) {
+        dependency = H5_OBJ_NAME(transformations) + "/" + ORIENTATION;
+
+        // If location dataset is written to group also, then dependency for
+        // orientation dataset containg the rotation transformation will be
+        // location. Else dependency for orientation is self.
+        std::string rotationDependency =
+            locationIsOrigin ? NO_DEPENDENCY
+                             : H5_OBJ_NAME(transformations) + "/" + LOCATION;
+        writeOrientation(transformations, rotation, rotationDependency);
+      }
+    }
+
+    writeStrDataset(childGroup, NAME, sourceName);
+    writeStrDataset(childGroup, DEPENDS_ON, dependency);
+  }
+
+  /*
+   * Function: monitor
+   * For NXinstrument parent (component info root). Produces an NXmonitor
+   * groups from Component info, and saves it in the parent
+   * group, along with the Nexus compliant datasets, and metadata stored in
+   * attributes to the new group.
+   *
+   * @param parentGroup : parent group in which to write the NXinstrument
+   * group.
+   * @param compInfo : componentInfo object.
+   * @param monitorID :  ID of the specific monitor.
+   * @param index :  index of the specific monitor in the Instrument cache.
+   * @return child group for further additions
+   */
+  H5::Group monitor(const H5::Group &parentGroup,
+                    const Geometry::ComponentInfo &compInfo,
+                    const int monitorId, const size_t index) {
+
+    // if the component is unnamed sets the name as unspecified with the
+    // location of the component in the cache
+    std::string nameInCache = compInfo.name(index);
+    std::string monitorName =
+        nameInCache.empty() ? "unspecified_monitor_" + std::to_string(index)
+                            : nameInCache;
+
+    Eigen::Vector3d position =
+        Mantid::Kernel::toVector3d(compInfo.position(index));
+    Eigen::Quaterniond rotation =
+        Mantid::Kernel::toQuaterniond(compInfo.rotation(index));
+
+    std::string dependency = NO_DEPENDENCY; // dependency initialiser
+
+    bool locationIsOrigin = isApproxZero(position, PRECISION);
+    bool orientationIsZero = isApproxZero(rotation, PRECISION);
+
+    H5::Group childGroup =
+        openOrCreateGroup(parentGroup, monitorName, NX_MONITOR);
+    writeStrAttribute(childGroup, NX_CLASS, NX_MONITOR);
+
+    // do not write NXtransformations if there is no translation or rotation
+    if (!(locationIsOrigin && orientationIsZero)) {
+      H5::Group transformations =
+          simpleNXSubGroup(childGroup, TRANSFORMATIONS, NX_TRANSFORMATIONS);
+
+      // self, ".", is the default first NXmonitor dependency in the chain.
+      // first check translation in NXmonitor is non-zero, and set dependency
+      // to location if true and write location. Then check if orientation in
+      // NXmonitor is non-zero, replace dependency with orientation if true.
+      // If neither orientation nor location are non-zero, NXmonitor is self
+      // dependent.
+      if (!locationIsOrigin) {
+        dependency = H5_OBJ_NAME(transformations) + "/" + LOCATION;
+        writeLocation(transformations, position);
+      }
+      if (!orientationIsZero) {
+        dependency = H5_OBJ_NAME(transformations) + "/" + ORIENTATION;
+
+        // If location dataset is written to group also, then dependency for
+        // orientation dataset containg the rotation transformation will be
+        // location. Else dependency for orientation is self.
+        std::string rotationDependency =
+            locationIsOrigin ? NO_DEPENDENCY
+                             : H5_OBJ_NAME(transformations) + "/" + LOCATION;
+        writeOrientation(transformations, rotation, rotationDependency);
+      }
+    }
+
+    H5::StrType dependencyStrType = strTypeOfSize(dependency);
+    writeNXMonitorNumber(childGroup, monitorId);
+
+    writeStrDataset(childGroup, BANK_NAME, monitorName);
+    writeStrDataset(childGroup, DEPENDS_ON, dependency);
+    return childGroup;
+  }
+
+  /* For NXinstrument parent (component info root). Produces an NXmonitor
+   * groups from Component info, and saves it in the parent
+   * group, along with the Nexus compliant datasets, and metadata stored in
+   * attributes to the new group.
+   *
+   * Saves detector-spectra mappings too
+   *
+   * @param parentGroup : parent group in which to write the NXinstrument
+   * group.
+   * @param compInfo : componentInfo object.
+   * @param monitorId :  ID of the specific monitor.
+   * @param index :  index of the specific monitor in the Instrument cache.
+   * @param mappings : Spectra to detector mappings
+   */
+  void monitor(const H5::Group &parentGroup,
+               const Geometry::ComponentInfo &compInfo, const int monitorId,
+               const size_t index, SpectraMappings &mappings) {
+
+    auto childGroup = monitor(parentGroup, compInfo, monitorId, index);
+    // Additional mapping information written.
+    writeDetectorCount(childGroup, mappings);
+    // Note that the detector list is the same as detector_number, but it is
+    // ordered by spectrum index 0 - N, whereas detector_number is just written
+    // out in the order the detectors are encountered in the bank.
+    writeDetectorList(childGroup, mappings);
+    writeDetectorIndex(childGroup, mappings);
+    writeSpectra(childGroup, mappings);
+  }
+
+  /*
+   * Function: detectors
+   * For NXinstrument parent (component info root). Save method which produces
+   * a set of NXdetctor groups from Component info detector banks, and saves
+   * it in the parent group, along with the Nexus compliant datasets, and
+   * metadata stored in attributes to the new group.
+   *
+   * @param parentGroup : parent group in which to write the NXinstrument
+   * group.
+   * @param compInfo : componentInfo object.
+   * @param detIDs : global detector IDs, from which those specific to the
+   * NXdetector will be extracted.
+   * @return childGroup for futher additions
+   */
+  H5::Group detector(const H5::Group &parentGroup,
+                     const Geometry::ComponentInfo &compInfo,
+                     const std::vector<int> &detIds, const size_t index) {
+
+    // if the component is unnamed sets the name as unspecified with the
+    // location of the component in the cache
+    std::string nameInCache = compInfo.name(index);
+    std::string detectorName =
+        nameInCache.empty() ? "unspecified_detector_at_" + std::to_string(index)
+                            : nameInCache;
+
+    Eigen::Vector3d position =
+        Mantid::Kernel::toVector3d(compInfo.position(index));
+    Eigen::Quaterniond rotation =
+        Mantid::Kernel::toQuaterniond(compInfo.rotation(index));
+
+    std::string dependency = NO_DEPENDENCY; // dependency initialiser
+
+    bool locationIsOrigin = isApproxZero(position, PRECISION);
+    bool orientationIsZero = isApproxZero(rotation, PRECISION);
+
+    H5::Group childGroup =
+        openOrCreateGroup(parentGroup, detectorName, NX_DETECTOR);
+    writeStrAttribute(childGroup, NX_CLASS, NX_DETECTOR);
+
+    // do not write NXtransformations if there is no translation or rotation
+    if (!(locationIsOrigin && orientationIsZero)) {
+      H5::Group transformations =
+          simpleNXSubGroup(childGroup, TRANSFORMATIONS, NX_TRANSFORMATIONS);
+
+      // self, ".", is the default first NXdetector dependency in the chain.
+      // first check translation in NXdetector is non-zero, and set dependency
+      // to location if true and write location. Then check if orientation in
+      // NXdetector is non-zero, replace dependency with orientation if true.
+      // If neither orientation nor location are non-zero, NXdetector is self
+      // dependent.
+      if (!locationIsOrigin) {
+        dependency = H5_OBJ_NAME(transformations) + "/" + LOCATION;
+        writeLocation(transformations, position);
+      }
+      if (!orientationIsZero) {
+        dependency = H5_OBJ_NAME(transformations) + "/" + ORIENTATION;
+
+        // If location dataset is written to group also, then dependency for
+        // orientation dataset containg the rotation transformation will be
+        // location. Else dependency for orientation is self.
+        std::string rotationDependency =
+            locationIsOrigin ? NO_DEPENDENCY
+                             : H5_OBJ_NAME(transformations) + "/" + LOCATION;
+        writeOrientation(transformations, rotation, rotationDependency);
+      }
+    }
+
+    H5::StrType dependencyStrType = strTypeOfSize(dependency);
+    writeXYZPixeloffset(childGroup, compInfo, index);
+    writeNXDetectorNumber(childGroup, compInfo, detIds, index);
+
+    writeStrDataset(childGroup, BANK_NAME, detectorName);
+    writeStrDataset(childGroup, DEPENDS_ON, dependency);
+    return childGroup;
+  }
+
+  /*
+   * Function: detectors
+   * For NXinstrument parent (component info root). Save method which produces
+   * a set of NXdetctor groups from Component info detector banks, and saves
+   * it in the parent group, along with the Nexus compliant datasets, and
+   * metadata stored in attributes to the new group.
+   *
+   * @param parentGroup : parent group in which to write the NXinstrument
+   * group.
+   * @param compInfo : componentInfo object.
+   * @param detIDs : global detector IDs, from which those specific to the
+   * @param index : current component index
+   * @param mappings : Spectra to detector mappings
+   * NXdetector will be extracted.
+   */
+  void detector(const H5::Group &parentGroup,
+                const Geometry::ComponentInfo &compInfo,
+                const std::vector<int> &detIds, const size_t index,
+                SpectraMappings &mappings) {
+
+    auto childGroup = detector(parentGroup, compInfo, detIds, index);
+
+    // Additional mapping information written.
+    writeDetectorCount(childGroup, mappings);
+    // Note that the detector list is the same as detector_number, but it is
+    // ordered by spectrum index 0 - N, whereas detector_number is just written
+    // out in the order the detectors are encountered in the bank.
+    writeDetectorList(childGroup, mappings);
+    writeDetectorIndex(childGroup, mappings);
+    writeSpectra(childGroup, mappings);
+  }
+
+private:
+  const Mode m_mode;
+
+  H5::Group openOrCreateGroup(const H5::Group &parent, const std::string &name,
+                              const std::string &classType) {
+
+    if (m_mode == Mode::Append) {
+      // Find by class and by name
+      auto results = utilities::findGroups(parent, classType);
+      for (auto &result : results) {
+        auto resultName = H5_OBJ_NAME(result);
+        // resultName gives full path. We match the last name on the path
+        if (std::regex_match(resultName, std::regex(".*/" + name + "$"))) {
+          return result;
+        }
+      }
+    }
+    // We can't find it, or we are writing from scratch anyway
+    return tryCreateGroup(parent, name);
+  }
+
+  // function to create a simple sub-group that has a nexus class attribute,
+  // inside a parent group.
+  H5::Group simpleNXSubGroup(H5::Group &parent, const std::string &name,
+                             const std::string &nexusAttribute) {
+    H5::Group subGroup = openOrCreateGroup(parent, name, nexusAttribute);
+    writeStrAttribute(subGroup, NX_CLASS, nexusAttribute);
+    return subGroup;
+  }
+}; // class NexusGeometrySaveImpl
+
+/*
+ * Function: saveInstrument
+ * calls the save methods to write components to file after exception
+ * checking. Produces a Nexus format file containing the Instrument geometry
+ * and metadata.
+ *
+ * @param compInfo : componentInfo object.
+ * @param detInfo : detectorInfo object.
+ * @param fullPath : save destination as full path.
+ * @param rootName : name of root entry
+ * @param logger : logging object
+ * @param append : append mode, means openting and appending to existing file.
+ * If false, creates new file.
+ * @param reporter : (optional) report to progressBase.
+ */
+void saveInstrument(const Geometry::ComponentInfo &compInfo,
+                    const Geometry::DetectorInfo &detInfo,
+                    const std::string &fullPath, const std::string &rootName,
+                    AbstractLogger &logger, bool append,
+                    Kernel::ProgressBase *reporter) {
+
+  validateInputs(logger, fullPath, compInfo);
+  // IDs of all detectors in Instrument
+  H5::Group rootGroup;
+  H5::H5File file;
+  if (append) {
+    file = H5::H5File(fullPath, H5F_ACC_RDWR); // open file
+    rootGroup = file.openGroup(rootName);
+  } else {
+    file = H5::H5File(fullPath, H5F_ACC_TRUNC); // open file
+    rootGroup = file.createGroup(rootName);
+  }
+
+  writeStrAttribute(rootGroup, NX_CLASS, NX_ENTRY);
+
+  using Mode = NexusGeometrySaveImpl::Mode;
+  NexusGeometrySaveImpl writer(append ? Mode::Append : Mode::Trunc);
+  // save and capture NXinstrument (component root)
+  H5::Group instrument = writer.instrument(rootGroup, compInfo);
+
+  // save NXsource
+  writer.source(instrument, compInfo);
+
+  // save NXsample
+  writer.sample(rootGroup, compInfo);
+
+  const auto &detIds = detInfo.detectorIDs();
+  // save NXdetectors
+  std::list<size_t> saved_indices;
+  // Looping from highest to lowest component index is critical
+  for (size_t index = compInfo.root() - 1; index >= detInfo.size(); --index) {
+    if (Geometry::ComponentInfoBankHelpers::isSaveableBank(compInfo, index)) {
+      if (isDesiredNXDetector(index, saved_indices, compInfo)) {
+        if (reporter != nullptr)
+          reporter->report();
+        writer.detector(instrument, compInfo, detIds, index);
+        saved_indices.push_back(index); // Now record the fact that children of
+                                        // this are not needed as NXdetectors
+      }
+    }
+  }
+
+  // save NXmonitors
+  for (size_t index = 0; index < detInfo.size(); ++index) {
+    if (detInfo.isMonitor(index)) {
+      if (reporter != nullptr)
+        reporter->report();
+      writer.monitor(instrument, compInfo, detIds[index], index);
+    }
+  }
+
+  file.close(); // close file
+
+} // saveInstrument
+
+/**
+ * Function: saveInstrument (overload)
+ * calls the save methods to write components to file after exception
+ * checking. Produces a Nexus format file containing the Instrument geometry
+ * and metadata.
+ *
+ * @param instrPair : instrument 2.0  object.
+ * @param fullPath : save destination as full path.
+ * @param rootName : name of root entry
+ * @param logger : logging object
+ * @param append : append mode, means openting and appending to existing file.
+ * If false, creates new file.
+ * @param reporter : (optional) report to progressBase.
+ */
+void saveInstrument(
+    const std::pair<std::unique_ptr<Geometry::ComponentInfo>,
+                    std::unique_ptr<Geometry::DetectorInfo>> &instrPair,
+    const std::string &fullPath, const std::string &rootName,
+    AbstractLogger &logger, bool append, Kernel::ProgressBase *reporter) {
+
+  const Geometry::ComponentInfo &compInfo = (*instrPair.first);
+  const Geometry::DetectorInfo &detInfo = (*instrPair.second);
+
+  return saveInstrument(compInfo, detInfo, fullPath, rootName, logger, append,
+                        reporter);
+}
+
+void saveInstrument(const Mantid::API::MatrixWorkspace &ws,
+                    const std::string &fullPath, const std::string &rootName,
+                    AbstractLogger &logger, bool append,
+                    Kernel::ProgressBase *reporter) {
+
+  const auto &detInfo = ws.detectorInfo();
+  const auto &compInfo = ws.componentInfo();
+
+  // Exception handling.
+  validateInputs(logger, fullPath, compInfo);
+  // IDs of all detectors in Instrument
+  const auto &detIds = detInfo.detectorIDs();
+
+  H5::Group rootGroup;
+  H5::H5File file;
+  if (append) {
+    file = H5::H5File(fullPath, H5F_ACC_RDWR); // open file
+    rootGroup = file.openGroup(rootName);
+  } else {
+    file = H5::H5File(fullPath, H5F_ACC_TRUNC); // open file
+    rootGroup = file.createGroup(rootName);
+  }
+
+  writeStrAttribute(rootGroup, NX_CLASS, NX_ENTRY);
+
+  using Mode = NexusGeometrySaveImpl::Mode;
+  NexusGeometrySaveImpl writer(append ? Mode::Append : Mode::Trunc);
+  // save and capture NXinstrument (component root)
+  H5::Group instrument = writer.instrument(rootGroup, compInfo);
+
+  // save NXsource
+  writer.source(instrument, compInfo);
+
+  // save NXsample
+  writer.sample(rootGroup, compInfo);
+
+  // save NXdetectors
+  auto detToIndexMap =
+<<<<<<< HEAD
+      ws.getDetectorIDToWorkspaceIndexMap(false /*do not throw if multiples*/);
+=======
+      ws.getDetectorIDToWorkspaceIndexMap(true /*throw if multiples*/);
+  // save NXdetectors
+>>>>>>> 0f9c9150d20a44750bfcea8438f981c177dd2cf7
+  std::list<size_t> saved_indices;
+  // Looping from highest to lowest component index is critical
+  for (size_t index = compInfo.root() - 1; index >= detInfo.size(); --index) {
+    if (Geometry::ComponentInfoBankHelpers::isSaveableBank(compInfo, index)) {
+
+      if (isDesiredNXDetector(index, saved_indices, compInfo)) {
+        // Make spectra detector mappings that can be used
+        SpectraMappings mappings =
+            makeMappings(compInfo, detToIndexMap, ws.indexInfo(),
+                         ws.spectrumInfo(), detIds, index);
+
+        if (reporter != nullptr)
+          reporter->report();
+        writer.detector(instrument, compInfo, detIds, index, mappings);
+        saved_indices.push_back(index); // Now record the fact that children of
+                                        // this are not needed as NXdetectors
+      }
+    }
+  }
+
+  // save NXmonitors
+  for (size_t index = 0; index < detInfo.size(); ++index) {
+    if (detInfo.isMonitor(index)) {
+      // Make spectra detector mappings that can be used
+      SpectraMappings mappings =
+          makeMappings(compInfo, detToIndexMap, ws.indexInfo(),
+                       ws.spectrumInfo(), detIds, index);
+
+      if (reporter != nullptr)
+        reporter->report();
+      writer.monitor(instrument, compInfo, detIds[index], index, mappings);
+    }
+  }
+
+  file.close(); // close file
+}
+
+// saveInstrument
+
+} // namespace NexusGeometrySave
+} // namespace NexusGeometry
+} // namespace Mantid
diff --git a/Framework/NexusGeometry/test/NexusGeometryParserTest.h b/Framework/NexusGeometry/test/NexusGeometryParserTest.h
index fc23bd8eee4a2d8599ffe3af601b301728aa25da..aa0a1eed0a8303147fc82b4ae1e95dc74074a3a6 100644
--- a/Framework/NexusGeometry/test/NexusGeometryParserTest.h
+++ b/Framework/NexusGeometry/test/NexusGeometryParserTest.h
@@ -66,7 +66,7 @@ public:
   void test_pixel_shape_as_mesh() {
 
     auto instrument = NexusGeometryParser::createInstrument(
-        "/home/spu92482/Downloads/DETGEOM_example_1.nxs",
+        "/Users/spu92482/Downloads/DETGEOM_example_1.nxs",
         std::make_unique<testing::NiceMock<MockLogger>>());
     auto beamline = extractBeamline(*instrument);
     auto &compInfo = *beamline.first;
@@ -80,12 +80,14 @@ public:
     TS_ASSERT(shape1Mesh);
     TS_ASSERT(shape2Mesh);
     TS_ASSERT_EQUALS(shape1Mesh, shape2Mesh); // pixel shape - all identical.
+    TSM_ASSERT_EQUALS("Same objects, same address", &shape1,
+                      &shape2); // Shapes are shared when identical
     TS_ASSERT_EQUALS(shape1Mesh->numberOfTriangles(), 2);
     TS_ASSERT_EQUALS(shape1Mesh->numberOfVertices(), 4);
   }
   void test_pixel_shape_as_cylinders() {
     auto instrument = NexusGeometryParser::createInstrument(
-        "/home/spu92482/Downloads/DETGEOM_example_2.nxs",
+        "/Users/spu92482/Downloads/DETGEOM_example_2.nxs",
         std::make_unique<testing::NiceMock<MockLogger>>());
     auto beamline = extractBeamline(*instrument);
     auto &compInfo = *beamline.first;
@@ -94,6 +96,8 @@ public:
     auto &shape1 = compInfo.shape(0);
     auto &shape2 = compInfo.shape(1);
 
+    TSM_ASSERT_EQUALS("Same objects, same address", &shape1,
+                      &shape2); // Shapes are shared when identical
     auto *shape1Cylinder =
         dynamic_cast<const Geometry::CSGObject *>(&shape1); // Test detectors
     auto *shape2Cylinder = dynamic_cast<const Geometry::CSGObject *>(&shape2);
@@ -109,7 +113,7 @@ public:
   }
   void test_detector_shape_as_mesh() {
     auto instrument = NexusGeometryParser::createInstrument(
-        "/home/spu92482/Downloads/DETGEOM_example_3.nxs",
+        "/Users/spu92482/Downloads/DETGEOM_example_3.nxs",
         std::make_unique<testing::NiceMock<MockLogger>>());
     auto beamline = extractBeamline(*instrument);
     auto &compInfo = *beamline.first;
@@ -117,19 +121,21 @@ public:
     TS_ASSERT_EQUALS(detInfo.size(), 4);
     auto &shape1 = compInfo.shape(0);
     auto &shape2 = compInfo.shape(1);
+    TSM_ASSERT_DIFFERS("Different objects, different addresses", &shape1,
+                       &shape2); // Shapes are not shared
     auto *shape1Mesh =
         dynamic_cast<const Geometry::MeshObject2D *>(&shape1); // Test detectors
     auto *shape2Mesh = dynamic_cast<const Geometry::MeshObject2D *>(&shape2);
     TS_ASSERT(shape1Mesh);
     TS_ASSERT(shape2Mesh);
-    TS_ASSERT_EQUALS(shape1Mesh, shape2Mesh); // pixel shape - all identical.
     TS_ASSERT_EQUALS(shape1Mesh->numberOfTriangles(), 1);
     TS_ASSERT_EQUALS(shape1Mesh->numberOfVertices(), 3);
-    // auto componentInfo = *beamline.first;
+    TS_ASSERT_EQUALS(shape2Mesh->numberOfTriangles(), 1);
+    TS_ASSERT_EQUALS(shape2Mesh->numberOfVertices(), 3);
   }
   void test_detector_shape_as_cylinders() {
     auto instrument = NexusGeometryParser::createInstrument(
-        "/home/spu92482/Downloads/DETGEOM_example_4.nxs",
+        "/Users/spu92482/Downloads/DETGEOM_example_4.nxs",
         std::make_unique<testing::NiceMock<MockLogger>>());
     auto beamline = extractBeamline(*instrument);
 
diff --git a/Framework/NexusGeometry/test/NexusGeometrySaveTest.h.orig b/Framework/NexusGeometry/test/NexusGeometrySaveTest.h.orig
new file mode 100644
index 0000000000000000000000000000000000000000..876b52172751aca7e535f6c66daf4ee6739edabb
--- /dev/null
+++ b/Framework/NexusGeometry/test/NexusGeometrySaveTest.h.orig
@@ -0,0 +1,1170 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef MANTID_NEXUSGEOMETRY_NEXUSGEOMETRYSAVETEST_H_
+#define MANTID_NEXUSGEOMETRY_NEXUSGEOMETRYSAVETEST_H_
+
+#include "MantidGeometry/Instrument/ComponentInfo.h"
+#include "MantidGeometry/Instrument/ComponentInfoBankHelpers.h"
+#include "MantidGeometry/Instrument/DetectorInfo.h"
+#include "MantidGeometry/Instrument/InstrumentVisitor.h"
+#include "MantidKernel/EigenConversionHelpers.h"
+#include "MantidNexusGeometry/NexusGeometryDefinitions.h"
+#include "MantidNexusGeometry/NexusGeometrySave.h"
+#include "MantidTestHelpers/ComponentCreationHelper.h"
+#include "MantidTestHelpers/FileResource.h"
+#include "MantidTestHelpers/NexusFileReader.h"
+
+#include "mockobjects.h"
+#include <cxxtest/TestSuite.h>
+#include <gmock/gmock.h>
+
+using namespace Mantid::NexusGeometry;
+
+class NexusGeometrySaveTest : public CxxTest::TestSuite {
+private:
+  testing::NiceMock<MockLogger> m_mockLogger;
+
+public:
+  // This pair of boilerplate methods prevent the suite being created statically
+  // This means the constructor isn't called when running other tests
+  static NexusGeometrySaveTest *createSuite() {
+    return new NexusGeometrySaveTest();
+  }
+  static void destroySuite(NexusGeometrySaveTest *suite) { delete suite; }
+
+  NexusGeometrySaveTest() {}
+
+  /*
+====================================================================
+
+IO PRECONDITIONS TESTS
+
+DESCRIPTION:
+
+The following tests are written to document the behaviour of the SaveInstrument
+method when a valid and invalid beamline Instrument is attempted to be saved
+out from memory to file. Included also are tests that document the behaviour
+when a valid (.nxs, .hdf5 ) or invalid output file extension is attempted to be
+used.
+
+====================================================================
+*/
+
+  void test_providing_invalid_path_throws() {
+
+    FileResource fileResource("invalid_path_to_file_test_file.hdf5");
+    const std::string badDestinationPath =
+        "false_directory\\" + fileResource.fullPath();
+
+    auto instrument = ComponentCreationHelper::createMinimalInstrument(
+        V3D(0, 0, -10), V3D(0, 0, 0), V3D(0, 0, 10));
+    auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
+
+    TS_ASSERT_THROWS(
+        NexusGeometrySave::saveInstrument(
+            instr, badDestinationPath, DEFAULT_ROOT_ENTRY_NAME, m_mockLogger),
+        std::invalid_argument &);
+  }
+
+  void test_progress_reporting() {
+
+    const int nbanks = 2;
+    MockProgressBase progressRep;
+    EXPECT_CALL(progressRep, doReport(testing::_))
+        .Times(nbanks); // Progress report once for each bank
+
+    FileResource fileResource("progress_report_test_file.hdf5");
+    std::string destinationFile = fileResource.fullPath();
+
+    auto instrument = ComponentCreationHelper::createTestInstrumentRectangular2(
+        nbanks /*number of banks*/, 2 /*number of pixels per bank*/);
+    auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
+
+    NexusGeometrySave::saveInstrument(instr, destinationFile,
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger,
+                                      false /*strict*/, &progressRep);
+    TS_ASSERT(testing::Mock::VerifyAndClearExpectations(&progressRep));
+  }
+
+  void test_false_file_extension_throws() {
+
+    FileResource fileResource("invalid_extension_test_file.abc");
+    std::string destinationFile = fileResource.fullPath();
+
+    auto instrument = ComponentCreationHelper::createMinimalInstrument(
+        V3D(0, 0, -10), V3D(0, 0, 0), V3D(0, 0, 10));
+    auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
+
+    TS_ASSERT_THROWS(NexusGeometrySave::saveInstrument(instr, destinationFile,
+                                                       DEFAULT_ROOT_ENTRY_NAME,
+                                                       m_mockLogger),
+                     std::invalid_argument &);
+    // Same error but log rather than throw
+    MockLogger logger;
+    EXPECT_CALL(logger, error(testing::_)).Times(1);
+    TS_ASSERT_THROWS(NexusGeometrySave::saveInstrument(
+                         instr, destinationFile, DEFAULT_ROOT_ENTRY_NAME,
+                         logger, false /*append*/),
+                     std::invalid_argument);
+    TS_ASSERT(testing::Mock::VerifyAndClearExpectations(&logger));
+  }
+
+  void test_instrument_without_sample_throws() {
+
+    auto const &instrument =
+        ComponentCreationHelper::createInstrumentWithOptionalComponents(
+            true, false, true);
+    auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
+
+    FileResource fileResource("check_no_sample_throws_test_file.hdf5");
+    auto destinationFile = fileResource.fullPath();
+
+    // instrument cache
+    auto const &compInfo = (*instr.first);
+
+    TS_ASSERT(compInfo.hasDetectorInfo()); // rule out throw by no detector info
+    TS_ASSERT(compInfo.hasSource());       // rule out throw by no source
+    TS_ASSERT(!compInfo.hasSample());      // verify component has no sample
+
+    TS_ASSERT_THROWS(NexusGeometrySave::saveInstrument(instr, destinationFile,
+                                                       DEFAULT_ROOT_ENTRY_NAME,
+                                                       m_mockLogger),
+                     std::invalid_argument &);
+
+    // Same error but log rather than throw
+    MockLogger logger;
+    EXPECT_CALL(logger, error(testing::_)).Times(1);
+    TS_ASSERT_THROWS(NexusGeometrySave::saveInstrument(
+                         instr, destinationFile, DEFAULT_ROOT_ENTRY_NAME,
+                         logger, false /*append*/),
+                     std::invalid_argument &);
+    TS_ASSERT(testing::Mock::VerifyAndClearExpectations(&logger));
+  }
+
+  void test_instrument_without_source_throws() {
+
+    auto const &instrument =
+        ComponentCreationHelper::createInstrumentWithOptionalComponents(
+            false, true, true);
+    auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
+
+    // instrument cache
+    auto &compInfo = (*instr.first);
+
+    FileResource fileResource("check_no_source_throws_test_file.hdf5");
+    auto destinationFile = fileResource.fullPath();
+
+    TS_ASSERT(compInfo.hasDetectorInfo()); // rule out throw by no detector info
+    TS_ASSERT(compInfo.hasSample());       // rule out throw by no sample
+    TS_ASSERT(!compInfo.hasSource());      // verify component has no source
+
+    TS_ASSERT_THROWS(NexusGeometrySave::saveInstrument(instr, destinationFile,
+                                                       DEFAULT_ROOT_ENTRY_NAME,
+                                                       m_mockLogger),
+                     std::invalid_argument &);
+    // Same error but log rather than throw
+    MockLogger logger;
+    EXPECT_CALL(logger, error(testing::_)).Times(1);
+    TS_ASSERT_THROWS(NexusGeometrySave::saveInstrument(
+                         instr, destinationFile, DEFAULT_ROOT_ENTRY_NAME,
+                         logger, false /*append*/),
+                     std::invalid_argument &);
+    TS_ASSERT(testing::Mock::VerifyAndClearExpectations(&logger));
+  }
+
+  void test_sample_not_at_origin_throws() {
+
+    auto instrument = ComponentCreationHelper::createMinimalInstrument(
+        V3D(0, 0, -10), V3D(0, 0, 2), V3D(0, 0, 10));
+    auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
+
+    FileResource fileResource("check_nxsource_group_test_file.hdf5");
+    std::string destinationFile = fileResource.fullPath();
+
+    TS_ASSERT_THROWS(NexusGeometrySave::saveInstrument(instr, destinationFile,
+                                                       DEFAULT_ROOT_ENTRY_NAME,
+                                                       m_mockLogger),
+                     std::invalid_argument &);
+    // Same error but log rather than throw
+    MockLogger logger;
+    EXPECT_CALL(logger, error(testing::_)).Times(1);
+    TS_ASSERT_THROWS(NexusGeometrySave::saveInstrument(
+                         instr, destinationFile, DEFAULT_ROOT_ENTRY_NAME,
+                         logger, false /*append*/),
+                     std::invalid_argument &);
+    TS_ASSERT(testing::Mock::VerifyAndClearExpectations(&logger));
+  }
+
+  /*
+ ====================================================================
+
+ NEXUS FILE FORMAT TESTS
+
+ DESCRIPTION:
+
+ The following tests document that the file format produced by saveInstrument is
+ compliant to the present Nexus standard as of the date corresponding to the
+ latest version of this document.
+
+ ====================================================================
+ */
+
+  void test_root_group_is_nxentry_class() {
+    // this test checks that the root group of the output file in saveInstrument
+    // has NXclass attribute of NXentry. as required by the Nexus file format.
+
+    // RAII file resource for test file destination
+    FileResource fileResource("check_nxentry_group_test_file.nxs");
+    std::string destinationFile = fileResource.fullPath();
+
+    // test instrument
+    auto instrument = ComponentCreationHelper::createMinimalInstrument(
+        V3D(0, 0, -10) /*source position*/, V3D(0, 0, 0) /*sample position*/,
+        V3D(0, 0, 10) /*bank position*/);
+    auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
+
+    NexusGeometrySave::saveInstrument(instr, destinationFile,
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
+
+    // test utility to check output file
+    NexusFileReader tester(destinationFile);
+
+    // assert the group at the root H5 path is NXentry
+    TS_ASSERT(tester.groupHasNxClass(NX_ENTRY, DEFAULT_ROOT_ENTRY_NAME));
+  }
+
+  void test_nxinstrument_group_exists_in_root_group() {
+    // this test checks that inside of the NXentry root group, the instrument
+    // data is saved to a group of NXclass NXinstrument
+
+    // RAII file resource for test file destination
+    FileResource fileResource("check_nxinstrument_group_test_file.hdf5");
+    std::string destinationFile = fileResource.fullPath();
+
+    // test instrument with some geometry
+    auto instrument = ComponentCreationHelper::createMinimalInstrument(
+        V3D(0, 0, -10) /*source position*/, V3D(0, 0, 0) /*sample position*/,
+        V3D(0, 0, 10) /*bank position*/);
+    auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
+
+    // call saveinstrument taking test instrument as parameter
+    NexusGeometrySave::saveInstrument(instr, destinationFile,
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
+
+    // test utility to check the output file
+    NexusFileReader tester(destinationFile);
+
+    // assert that inside a group with attribute NXentry, which as per the
+    // previous test we know to be the root group, there exists a group of
+    // NXclass NXinstrument.
+    TS_ASSERT(tester.parentNXgroupHasChildNXgroup(NX_ENTRY, NX_INSTRUMENT));
+  }
+
+  void test_NXInstrument_name_is_aways_instrument() {
+    // NXInstrument group name is always written as "instrument" for legacy
+    // compatibility reasons
+
+    // RAII file resource for test file destination
+    FileResource fileResource("check_instrument_name_test_file.nxs");
+    auto destinationFile = fileResource.fullPath();
+
+    // test instrument
+    auto instrument = ComponentCreationHelper::createMinimalInstrument(
+        V3D(0, 0, -10) /*source position*/, V3D(0, 0, 0) /*sample position*/,
+        V3D(0, 0, 10) /*bank position*/);
+
+    // set name of instrument
+    instrument->setName("test_instrument_name");
+    auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
+
+    // call saveInstrument passing the test instrument as parameter.
+    NexusGeometrySave::saveInstrument(instr, destinationFile,
+                                      DEFAULT_ROOT_ENTRY_NAME,
+                                      m_mockLogger); // saves instrument
+
+    // test utility to check the output file.
+    NexusFileReader testUtility(destinationFile);
+
+    const auto &compInfo = *instr.first;
+
+    // full H5 path to the NXinstrument group
+    FullNXPath path = {DEFAULT_ROOT_ENTRY_NAME, compInfo.name(compInfo.root())};
+
+    // assert no exception thrown on open of instrument group in file with
+    // manually set name.
+    TS_ASSERT_THROWS_NOTHING(testUtility.openfullH5Path(path));
+
+    // assert group is indeed NXinstrument.
+    TS_ASSERT(testUtility.hasNXAttributeInGroup(NX_INSTRUMENT, path));
+
+    // assert the dataset containing the instrument name has been correctly
+    // stored also.
+    TS_ASSERT(testUtility.dataSetHasStrValue(
+        NAME, compInfo.name(compInfo.root()), path));
+  }
+
+<<<<<<< HEAD
+  void
+  test_NXclass_without_name_is_assigned_unique_default_name_for_each_group() {
+    // this test will try to save and unnamed instrument with multiple unnamed
+    // detector banks, to verify that the unique group names which
+    // saveInstrument provides for each NXclass do not throw a H5 error due to
+    // duplication of group names. If any group in the same tree path share the
+    // same name, HDF5 will throw a group exception. In this test, we expect no
+    // such exception to throw.
+
+    // RAII file resource for test file destination.
+    FileResource fileResource("default_group_names_test.hdf5");
+    std::string destinationFile = fileResource.fullPath();
+
+    // unnamed ("") instrument with multiple unnamed detector banks ("")
+    auto instrument = ComponentCreationHelper::createTestInstrumentRectangular2(
+        2 /*number of banks*/, 2 /*number of pixels*/, 0.008, true);
+    auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
+
+    TS_ASSERT_THROWS_NOTHING(NexusGeometrySave::saveInstrument(
+        instr, destinationFile, DEFAULT_ROOT_ENTRY_NAME, m_mockLogger));
+  }
+
+=======
+>>>>>>> 76b68e5e679f2310789e1d43e5d8aecf093666fa
+  void test_nxsource_group_exists_and_is_in_nxinstrument_group() {
+    // this test checks that inside of the NXinstrument group, the the source
+    // data is saved to a group of NXclass NXsource
+
+    // RAII file resource for test file destination.
+    FileResource fileResource("check_nxsource_group_test_file.hdf5");
+    std::string destinationFile = fileResource.fullPath();
+
+    // test instrument
+    auto instrument = ComponentCreationHelper::createMinimalInstrument(
+        V3D(0, 0, -10) /*source position*/, V3D(0, 0, 0) /*sample position*/,
+        V3D(0, 0, 10) /*bank position*/);
+    auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
+
+    // call saveInstrument passing test instrument as parameter
+    NexusGeometrySave::saveInstrument(instr, destinationFile,
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
+
+    // test utility to check output file
+    NexusFileReader tester(destinationFile);
+
+    // assert that inside a group with attribute NXinstrument, which as per the
+    // previous test we know to be the instrument group, there exists a group of
+    // NXclass NXsource.
+    TS_ASSERT(tester.parentNXgroupHasChildNXgroup(NX_INSTRUMENT, NX_SOURCE));
+  }
+
+  void test_nxsample_group_exists_and_is_in_nxentry_group() {
+
+    FileResource fileResource("check_nxsource_group_test_file.hdf5");
+    std::string destinationFile = fileResource.fullPath();
+
+    auto instrument = ComponentCreationHelper::createMinimalInstrument(
+        V3D(0, 0, -10) /*source position*/, V3D(0, 0, 0) /*sample position*/,
+        V3D(0, 0, 10) /*bank position*/);
+    auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
+
+    // instrument cache
+    auto &compInfo = (*instr.first);
+
+    NexusGeometrySave::saveInstrument(instr, destinationFile,
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
+    NexusFileReader tester(destinationFile);
+
+    TS_ASSERT(compInfo.hasSample());
+    TS_ASSERT(tester.parentNXgroupHasChildNXgroup(NX_ENTRY, NX_SAMPLE));
+  }
+
+  void test_correct_number_of_detectors_saved() {
+
+    ScopedFileHandle fileResource("check_num_of_banks_test.hdf5");
+    std::string destinationFile = fileResource.fullPath();
+
+    int banksInInstrument = 3;
+
+    auto instrument = ComponentCreationHelper::createTestInstrumentRectangular2(
+        banksInInstrument /*number of banks*/, 4 /*pixels (arbitrary)*/);
+    auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
+
+    NexusGeometrySave::saveInstrument(instr, destinationFile,
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
+    NexusFileReader tester(destinationFile);
+    FullNXPath path = {DEFAULT_ROOT_ENTRY_NAME,
+                       "basic_rect" /*instrument name*/};
+
+    int numOfNXDetectors = tester.countNXgroup(path, NX_DETECTOR);
+
+    TS_ASSERT_EQUALS(numOfNXDetectors, banksInInstrument);
+  }
+
+  /*
+====================================================================
+
+NEXUS TRANSFOMATIONS TESTS
+
+DESCRIPTION:
+
+The following tests document that saveInstrument will find and write detectors
+and other Instrument components to file in Nexus format, and where there exists
+transformations in ComponentInfo and DetectorInfo, SaveInstrument will generate
+'NXtransformations' groups to contain the corresponding component
+rotations/translations, and pixel offsets in any 'NXdetector' found in the
+Instrument cache.
+
+====================================================================
+*/
+
+  void
+  test_rotation_of_NXdetector_written_to_file_is_same_as_in_component_info() {
+
+    /*
+   test scenario: pass into saveInstrument an instrument with manually set
+   non-zero rotation in a detector bank. Expectation: test utilty will search
+   file for orientaion dataset, read the magnitude of the angle, and the axis
+   vector. The output quaternion from file will be compared to the input
+   quaternion manually set. Asserts that they are approximately equal,
+   indicating that saveinstrument has correctly written the orientation data.
+   */
+
+    // RAII file resource for test file destination
+    FileResource fileResource(
+        "check_rotation_written_to_nxdetector_test_file.hdf5");
+    std::string destinationFile = fileResource.fullPath();
+
+    // prepare rotation for instrument
+    const Quat bankRotation(15, V3D(0, 1, 0));
+    const Quat detRotation(30, V3D(0, 1, 0));
+
+    // create test instrument and get cache
+    auto instrument =
+        ComponentCreationHelper::createSimpleInstrumentWithRotation(
+            Mantid::Kernel::V3D(0, 0, -10), Mantid::Kernel::V3D(0, 0, 0),
+            Mantid::Kernel::V3D(0, 0, 10), bankRotation, detRotation);
+    auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
+
+    // call saveInstrument
+    NexusGeometrySave::saveInstrument(instr, destinationFile,
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
+
+    // instance of test utility to check saved file
+    NexusFileReader tester(destinationFile);
+
+    // full path to group to be opened in test utility
+    FullNXPath path = {
+        DEFAULT_ROOT_ENTRY_NAME,
+        "test-instrument-with-detector-rotations" /*instrument name*/,
+        "detector-stage" /*bank name*/, TRANSFORMATIONS};
+
+    // get angle magnitude in dataset
+    double angleInFile = tester.readDoubleFromDataset(ORIENTATION, path);
+
+    // get axis or rotation
+    std::string attributeName = "vector";
+    std::vector<double> axisInFile = tester.readDoubleVectorFrom_d_Attribute(
+        attributeName, ORIENTATION, path);
+    V3D axisVectorInFile = {axisInFile[0], axisInFile[1], axisInFile[2]};
+
+    // Eigen copy of bankRotation for assertation
+    Eigen::Quaterniond bankRotationCopy =
+        Mantid::Kernel::toQuaterniond(bankRotation);
+
+    // bank rotation in file as eigen Quaternion for assertation
+    Eigen::Quaterniond rotationInFile =
+        Mantid::Kernel::toQuaterniond(Quat(angleInFile, axisVectorInFile));
+
+    TS_ASSERT(rotationInFile.isApprox(bankRotationCopy));
+  }
+
+  void
+  test_rotation_of_NXmonitor_written_to_file_is_same_as_in_component_info() {
+
+    /*
+    test scenario: pass into saveInstrument an instrument with manually set
+    non-zero rotation in a monitor. Expectation: test utilty will search
+    file for orientaion dataset, read the magnitude of the angle, and the
+    axis vector. The output quaternion from file will be compared to the
+    input quaternion manually set. Asserts that they are approximately equal,
+    indicating that saveinstrument has correctly written the orientation
+    data.
+    */
+
+    // RAII file resource for test file destination
+    FileResource fileResource(
+        "check_rotation_written_to_nx_monitor_test_file.hdf5");
+    std::string destinationFile = fileResource.fullPath();
+
+    // prepare rotation for instrument
+    const V3D monitorPosition(0, 1, 0);
+    const Quat monitorRotation(30, V3D(0, 1, 0));
+
+    // create test instrument and get cache
+    auto instrument =
+        ComponentCreationHelper::createMinimalInstrumentWithMonitor(
+            monitorPosition, monitorRotation);
+    auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
+
+    // call saveInstrument
+    NexusGeometrySave::saveInstrument(instr, destinationFile,
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
+
+    // instance of test utility to check saved file
+    NexusFileReader tester(destinationFile);
+
+    // full path to group to be opened in test utility
+    FullNXPath path = {DEFAULT_ROOT_ENTRY_NAME, "test-instrument-with-monitor",
+                       "test-monitor", TRANSFORMATIONS};
+
+    // get angle magnitude in dataset
+    double angleInFile = tester.readDoubleFromDataset(ORIENTATION, path);
+
+    // get axis or rotation
+    std::string attributeName = "vector";
+    std::vector<double> axisInFile = tester.readDoubleVectorFrom_d_Attribute(
+        attributeName, ORIENTATION, path);
+    V3D axisVectorInFile = {axisInFile[0], axisInFile[1], axisInFile[2]};
+
+    // Eigen copy of monitorRotation for assertation
+    Eigen::Quaterniond monitorRotationCopy =
+        Mantid::Kernel::toQuaterniond(monitorRotation);
+
+    // bank rotation in file as eigen Quaternion for assertation
+    Eigen::Quaterniond rotationInFile =
+        Mantid::Kernel::toQuaterniond(Quat(angleInFile, axisVectorInFile));
+
+    TS_ASSERT(rotationInFile.isApprox(monitorRotationCopy));
+  }
+
+  void test_location_written_to_file_is_same_as_in_component_info() {
+
+    /*
+    test scenario: pass into saveInstrument an instrument with manually set
+    non-zero translation in the source. Expectation: test utilty will search
+    file for location dataset, read the norm of the vector, and the unit vector.
+    The output vector from file will be compared to the input vector manually
+    set. Asserts that they are approximately equal, indicating that
+    saveinstrument has correctly written the location data.
+    */
+
+    // RAII file resource for test file destination
+    FileResource fileResource(
+        "check_location_written_to_nxsource_test_file.hdf5");
+    std::string destinationFile = fileResource.fullPath();
+
+    // prepare location for instrument
+    const V3D sourceLocation(0, 0, 10);
+
+    // create test instrument and get cache
+    auto instrument =
+        ComponentCreationHelper::createInstrumentWithSourceRotation(
+            sourceLocation, Mantid::Kernel::V3D(0, 0, 0),
+            Mantid::Kernel::V3D(0, 0, 10), Quat(90, V3D(0, 1, 0)));
+    auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
+
+    // call saveInstrument
+    NexusGeometrySave::saveInstrument(instr, destinationFile,
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
+
+    // instance of test utility to check saved file
+    NexusFileReader tester(destinationFile);
+
+    // full path to group to be opened in test utility
+    FullNXPath path = {DEFAULT_ROOT_ENTRY_NAME,
+                       "test-instrument" /*instrument name*/,
+                       "source" /*source name*/, TRANSFORMATIONS};
+
+    // get magnitude of vector in dataset
+    double normInFile = tester.readDoubleFromDataset(LOCATION, path);
+
+    // get axis or rotation
+    std::string attributeName = "vector";
+    std::vector<double> data =
+        tester.readDoubleVectorFrom_d_Attribute(attributeName, LOCATION, path);
+    Eigen::Vector3d unitVecInFile = {data[0], data[1], data[2]};
+
+    // Eigen copy of sourceRotation for assertation
+    Eigen::Vector3d sourceLocationCopy =
+        Mantid::Kernel::toVector3d(sourceLocation);
+
+    auto positionInFile = normInFile * unitVecInFile;
+
+    TS_ASSERT(positionInFile.isApprox(sourceLocationCopy));
+  }
+
+  void
+  test_rotation_of_nx_source_written_to_file_is_same_as_in_component_info() {
+
+    /*
+    test scenario: pass into saveInstrument an instrument with manually set
+    non-zero rotation in the source. Expectation: test utilty will search file
+    for orientaion dataset, read the magnitude of the angle, and the axis
+    vector. The output quaternion from file will be compared to the input
+    quaternion manually set. Asserts that they are approximately equal,
+    indicating that saveinstrument has correctly written the orientation data.
+    */
+
+    // RAII file resource for test file destination
+    FileResource fileResource(
+        "check_rotation_written_to_nxsource_test_file.hdf5");
+    std::string destinationFile = fileResource.fullPath();
+
+    // prepare rotation for instrument
+    const Quat sourceRotation(90, V3D(0, 1, 0));
+
+    // create test instrument and get cache
+    auto instrument =
+        ComponentCreationHelper::createInstrumentWithSourceRotation(
+            Mantid::Kernel::V3D(0, 0, -10), Mantid::Kernel::V3D(0, 0, 0),
+            Mantid::Kernel::V3D(0, 0, 10), sourceRotation);
+    auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
+
+    // call saveInstrument
+    NexusGeometrySave::saveInstrument(instr, destinationFile,
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
+
+    // instance of test utility to check saved file
+    NexusFileReader tester(destinationFile);
+
+    // full path to group to be opened in test utility
+    FullNXPath path = {DEFAULT_ROOT_ENTRY_NAME,
+                       "test-instrument" /*instrument name*/,
+                       "source" /*source name*/, TRANSFORMATIONS};
+
+    // get angle magnitude in dataset
+    double angleInFile = tester.readDoubleFromDataset(ORIENTATION, path);
+
+    // get axis or rotation
+    std::string attributeName = "vector";
+    std::vector<double> axisInFile = tester.readDoubleVectorFrom_d_Attribute(
+        attributeName, ORIENTATION, path);
+    V3D axisVectorInFile = {axisInFile[0], axisInFile[1], axisInFile[2]};
+
+    // Eigen copy of sourceRotation for assertation
+    Eigen::Quaterniond sourceRotationCopy =
+        Mantid::Kernel::toQuaterniond(sourceRotation);
+
+    // source rotation in file as eigen Quaternion for assertation
+    Eigen::Quaterniond rotationInFile =
+        Mantid::Kernel::toQuaterniond(Quat(angleInFile, axisVectorInFile));
+
+    TS_ASSERT(rotationInFile.isApprox(sourceRotationCopy));
+  }
+
+  void
+  test_an_nx_class_location_is_not_written_when_component_position_is_at_origin() {
+
+    /*
+    test scenario: pass into saveInstrument an instrument with zero source
+    translation. Inspection: test utilty will search file for location
+    dataset and should return false, indicating that saveInstrument
+    identified the transformation as effectively zero, and did not write the
+    transformation to file
+    */
+
+    // RAII file resource for test file destination
+    FileResource fileResource("origin_nx_source_location_file_test.hdf5");
+    std::string destinationFile = fileResource.fullPath();
+
+    // prepare geometry for instrument
+    const V3D detectorLocation(0, 0, 10);
+    const V3D sourceLocation(0, 0, 0); // set to zero for test
+    const Quat sourceRotation(90, V3D(0, 1, 0));
+
+    // create test instrument and get cache
+    auto instrument =
+        ComponentCreationHelper::createInstrumentWithSourceRotation(
+            sourceLocation, Mantid::Kernel::V3D(0, 0, 0), detectorLocation,
+            sourceRotation); // source rotation
+    auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
+
+    // call saveInstrument
+    NexusGeometrySave::saveInstrument(instr, destinationFile,
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
+
+    // instance of test utility to check saved file
+    NexusFileReader tester(destinationFile);
+
+    // full path to group to be opened in test utility
+    FullNXPath path = {DEFAULT_ROOT_ENTRY_NAME,
+                       "test-instrument" /*instrument name*/,
+                       "source" /*source name*/, TRANSFORMATIONS};
+
+    // assertations
+    bool hasLocation = tester.hasDataset(LOCATION, path);
+    TS_ASSERT(!hasLocation);
+  }
+
+  void test_nx_detector_rotation_not_written_when_is_zero() {
+
+    /*
+   test scenario: pass into saveInstrument an instrument with zero detector bank
+   rotation. Inspection: test utilty will search file for orientation
+   dataset and should return false, indicating that saveInstrument
+   identified the transformation as effectively zero, and did not write the
+   transformation to file
+   */
+
+    const V3D detectorLocation(0, 0, 10); // arbitrary non-zero
+    const V3D sourceLocation(0, 0, -10);  // arbitrary
+
+    const Quat someRotation(30, V3D(1, 0, 0)); // arbitrary
+    const Quat bankRotation(0, V3D(0, 0, 1));  // set (angle) to zero
+
+    // RAII file resource for test file destination
+    FileResource fileResource("zero_nx_detector_rotation_file_test.hdf5");
+    std::string destinationFile = fileResource.fullPath();
+
+    // test instrument with zero source rotation
+    auto instrument =
+        ComponentCreationHelper::createSimpleInstrumentWithRotation(
+            sourceLocation, Mantid::Kernel::V3D(0, 0, 0), detectorLocation,
+            bankRotation, someRotation);
+    auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
+
+    // full path to access NXtransformations group with test utility
+    FullNXPath path = {
+        DEFAULT_ROOT_ENTRY_NAME,
+        "test-instrument-with-detector-rotations" /*instrument name*/,
+        "detector-stage" /*bank name*/, TRANSFORMATIONS};
+
+    // call saveInstrument passing test instrument as parameter
+    NexusGeometrySave::saveInstrument(instr, destinationFile,
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
+
+    // test utility to check output file
+    NexusFileReader tester(destinationFile);
+
+    // assert rotation not written to file
+    bool hasRotation = tester.hasDataset(ORIENTATION, path);
+    TS_ASSERT(!hasRotation);
+  }
+
+  void test_nx_monitor_rotation_not_written_when_is_zero() {
+
+    /*
+    test scenario: pass into saveInstrument an instrument with zero monitor
+    rotation. Inspection: test utilty will search file for orientation dataset
+    and should return false, indicating that saveInstrument identified the
+    transformation as effectively zero, and did not write the transformation to
+    file
+    */
+
+    // RAII file resource for test file destination
+    FileResource fileResource("zero_nx_monitor_rotation_file_test.hdf5");
+    std::string destinationFile = fileResource.fullPath();
+
+    V3D someLocation(0.0, 0.0, -5.0); // arbitrary monitor location
+
+    // test instrument with zero monitor rotation
+    auto instrument =
+        ComponentCreationHelper::createMinimalInstrumentWithMonitor(
+            someLocation, Quat(0, V3D(0, 1, 0)) /*monitor rotation of zero*/);
+    auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
+
+    // full path to group to be opened in test utility
+    FullNXPath path = {DEFAULT_ROOT_ENTRY_NAME, "test-instrument-with-monitor",
+                       "test-monitor", TRANSFORMATIONS};
+
+    // call saveInstrument passing test instrument as parameter
+    NexusGeometrySave::saveInstrument(instr, destinationFile,
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
+
+    // test utility to check output file
+    NexusFileReader tester(destinationFile);
+
+    // assert that no dataset named 'orientation' exists in output file
+    bool hasRotation = tester.hasDataset(ORIENTATION, path);
+    TS_ASSERT(!hasRotation);
+  }
+
+  void test_source_rotation_not_written_when_is_zero() {
+
+    /*
+    test scenario: pass into saveInstrument an instrument with zero source
+    rotation. Inspection: test utilty will search file for orientation dataset
+    and should return false, indicating that saveInstrument identified the
+    transformation as effectively zero, and did not write the transformation to
+    file
+    */
+
+    // geometry for test instrument
+    const V3D detectorLocation(0, 0, 10);
+    const V3D sourceLocation(-10, 0, 0);
+    const Quat sourceRotation(0, V3D(0, 0, 1)); // set (angle) to zero
+
+    // RAII file resource for test file destination
+    FileResource inFileResource("zero_nx_source_rotation_file_test.hdf5");
+    std::string destinationFile = inFileResource.fullPath();
+
+    // test instrument with zero rotation
+    auto instrument =
+        ComponentCreationHelper::createInstrumentWithSourceRotation(
+            sourceLocation, Mantid::Kernel::V3D(0, 0, 0), detectorLocation,
+            sourceRotation); // source rotation
+    auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
+
+    // full path to group to be opened in test utility
+    FullNXPath path = {DEFAULT_ROOT_ENTRY_NAME, "test-instrument", "source",
+                       TRANSFORMATIONS};
+
+    // call saveinstrument passing test instrument as parameter
+    NexusGeometrySave::saveInstrument(instr, destinationFile,
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
+
+    // test utility to check output file
+    NexusFileReader tester(destinationFile);
+
+    // assert dataset 'orientation' doesnt exist
+    bool hasRotation = tester.hasDataset(ORIENTATION, path);
+    TS_ASSERT(!hasRotation);
+  }
+
+  void
+  test_xyz_pixel_offset_in_file_is_relative_position_from_bank_without_bank_transformations() {
+
+    // this test will check that the pixel offsets are stored as their positions
+    // relative to the parent bank, ignoring any transformations
+
+    /*
+    test scenario: instrument with manually set pixel offset passed into
+    saveInstrument. Inspection: xyz pixel offset written in file matches the
+    manually set offset.
+    */
+
+    // create RAII file resource for testing
+    FileResource fileResource("check_pixel_offset_format_test_file.hdf5");
+    std::string destinationFile = fileResource.fullPath();
+
+    // prepare geometry for instrument
+    const Quat relativeBankRotation(45.0, V3D(0.0, 1.0, 0.0));
+    const Quat relativeDetRotation(45.0, V3D(0.0, 1.0, 0.0));
+    const V3D absBankposition(0, 0, 10);
+    const V3D relativeDetposition(2.0, -2.0, 0.0); // i.e. pixel offset
+
+    // create test instrument with one bank consisting of one detector (pixel)
+    auto instrument =
+        ComponentCreationHelper::createSimpleInstrumentWithRotation(
+            Mantid::Kernel::V3D(0, 0, -10), // source position
+            Mantid::Kernel::V3D(0, 0, 0),   // sample position
+            absBankposition,                // bank position
+            relativeBankRotation,           // bank rotation
+            relativeDetRotation,            // detector (pixel) rotation
+            relativeDetposition);           // detector (pixel) position
+    auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
+
+    // call save insrument passing the test instrument as parameter
+    NexusGeometrySave::saveInstrument(instr, destinationFile,
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
+
+    // instance of test utility to check saved file
+    NexusFileReader tester(destinationFile);
+    FullNXPath path = {
+        DEFAULT_ROOT_ENTRY_NAME,
+        "test-instrument-with-detector-rotations" /*instrument name*/,
+        "detector-stage" /*bank name*/};
+
+    // initalise to zero for case when an offset is not written to file,
+    // thus its values are zero
+
+    // read the xyz offset of the pixel from the output file
+    double pixelOffsetX = tester.readDoubleFromDataset(X_PIXEL_OFFSET, path);
+    double pixelOffsetY = tester.readDoubleFromDataset(Y_PIXEL_OFFSET, path);
+
+    // implicitly assert that z offset is zero, and not written to file, as
+    // demonstrated in eairlier tests, where the same method is apled for the
+    // pixel offsets.
+    TS_ASSERT(!tester.hasDataset(Z_PIXEL_OFFSET, path));
+
+    // store offset in this bank to Eigen vector for testing
+    Eigen::Vector3d offsetInFile(pixelOffsetX, pixelOffsetY, 0);
+
+    // assert the offset in the file is approximately the same as that specified
+    // manually. thus the offset written by saveInstrument has removed the
+    // transformations of the bank
+    TS_ASSERT(
+        offsetInFile.isApprox(Mantid::Kernel::toVector3d(relativeDetposition)));
+  }
+
+  /*
+  ====================================================================
+
+  DEPENDENCY CHAIN TESTS
+
+  DESCRIPTION:
+  The following tests document that saveInstrument will write the
+  NXtransformations dependencies as specified in the Mantid Instrument
+  Definition file, which says that if a translation and rotation exists, the
+  translation precedes the rotation, so that the NXclass depends on dataset
+  'orientation', which depends on dataset 'location'. If only one
+  NXtransformation exists, the NXclass group will depend on it. Finally, if no
+  NXtransformations are present, the NXclass group will be self dependent.
+
+  ====================================================================
+  */
+
+  void
+  test_when_location_is_not_written_and_orientation_exists_dependency_is_orientation_path_and_orientation_is_self_dependent() {
+
+    // USING SOURCE FOR DEMONSTRATION.
+
+    /*
+        test scenario: saveInstrument called with zero translation, and some
+    non-zero rotation in source. Expected behaviour is: (dataset) 'depends_on'
+    has value /absoulute/path/to/orientation, and (dataset) 'orientation' has
+    dAttribute (AKA attribute of dataset) 'depends_on' with value "."
+    */
+
+    // geometry for test instrument
+    const V3D detectorLocation(0, 0, 10);        // arbitrary
+    const Quat sourceRotation(90, V3D(0, 1, 0)); // arbitrary
+    const V3D sourceLocation(0, 0, 0);           // set to zero
+
+    // create RAII file resource for testing
+    FileResource fileResource("no_location_dependency_test.hdf5");
+    std::string destinationFile = fileResource.fullPath();
+
+    // test instrument with location of source at zero
+    auto instrument =
+        ComponentCreationHelper::createInstrumentWithSourceRotation(
+            sourceLocation,
+            Mantid::Kernel::V3D(0, 0, 0) /*sample position at zero*/,
+            detectorLocation, sourceRotation);
+    auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
+
+    FullNXPath transformationsPath = {
+        DEFAULT_ROOT_ENTRY_NAME, "test-instrument" /*instrument name*/,
+        "source" /*source name*/, TRANSFORMATIONS};
+
+    FullNXPath sourcePath = transformationsPath;
+    sourcePath.pop_back(); // source path is one level abve transformationsPath
+
+    // call saveInstrument with test instrument as parameter
+    NexusGeometrySave::saveInstrument(instr, destinationFile,
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
+
+    // test utility to check output file
+    NexusFileReader tester(destinationFile);
+
+    // assert what there is no 'location' dataset in NXtransformations, but
+    // there is the dataset 'orientation', confirming that saveInstrument has
+    // skipped writing a zero translation.
+    bool hasLocation = tester.hasDataset(LOCATION, transformationsPath);
+    bool hasOrientation = tester.hasDataset(ORIENTATION, transformationsPath);
+    TS_ASSERT(hasOrientation); // assert orientation dataset exists.
+    TS_ASSERT(!hasLocation);   // assert location dataset doesn't exist.
+
+    // assert that the NXsource depends on dataset 'orientation' in the
+    // transformationsPath, since the dataset exists.
+    bool sourceDependencyIsOrientation = tester.dataSetHasStrValue(
+        DEPENDS_ON, toNXPathString(transformationsPath) + "/" + ORIENTATION,
+        sourcePath);
+    TS_ASSERT(sourceDependencyIsOrientation);
+
+    // assert that the orientation depends on itself, since not translation is
+    // present
+    bool orientationDependencyIsSelf = tester.hasAttributeInDataSet(
+        ORIENTATION, DEPENDS_ON, NO_DEPENDENCY, transformationsPath);
+    TS_ASSERT(orientationDependencyIsSelf);
+  }
+
+  void
+  test_when_orientation_is_not_written_and_location_exists_dependency_is_location_path_and_location_is_self_dependent() {
+
+    // USING SOURCE FOR DEMONSTRATION.
+
+    /*
+    test scenario: saveInstrument called with zero rotation, and some
+    non-zero translation in source. Expected behaviour is: (dataset)
+   'depends_on' has value "/absoulute/path/to/location", and (dataset)
+   'location' has dAttribute (AKA attribute of dataset) 'depends_on' with
+   value "."
+    */
+
+    // Geometry for test instrument
+    const V3D detectorLocation(0.0, 0.0, 10.0);         // arbitrary
+    const V3D sourceLocation(0.0, 0.0, -10.0);          // arbitrary
+    const Quat sourceRotation(0.0, V3D(0.0, 1.0, 0.0)); // set to zero
+
+    // create RAII file resource for testing
+    FileResource fileResource("no_orientation_dependency_test.hdf5");
+    std::string destinationFile = fileResource.fullPath();
+
+    // test instrument with rotation of source of zero
+    auto instrument =
+        ComponentCreationHelper::createInstrumentWithSourceRotation(
+            sourceLocation,
+            Mantid::Kernel::V3D(0.0, 0.0, 0.0) /*samle position*/,
+            detectorLocation, sourceRotation);
+    auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
+
+    FullNXPath transformationsPath = {
+        DEFAULT_ROOT_ENTRY_NAME, "test-instrument" /*instrument name*/,
+        "source" /*source name*/, TRANSFORMATIONS};
+
+    FullNXPath sourcePath = transformationsPath;
+    sourcePath.pop_back(); // source path is one level abve transformationsPath
+
+    // call saveInstrument with test instrument as parameter
+    NexusGeometrySave::saveInstrument(instr, destinationFile,
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
+
+    // test utility for checking file
+    NexusFileReader tester(destinationFile);
+
+    // assert what there is no 'orientation' dataset in NXtransformations, but
+    // there is the dataset 'location', confirming that saveInstrument has
+    // skipped writing a zero reotation.
+    bool hasLocation = tester.hasDataset(LOCATION, transformationsPath);
+    bool hasOrientation = tester.hasDataset(ORIENTATION, transformationsPath);
+    TS_ASSERT(!hasOrientation); // assert orientation dataset doesn't exist.
+    TS_ASSERT(hasLocation);     // assert location dataset exists.
+
+    // assert that the NXsource depends on dataset 'location' in the
+    // transformationsPath, since the dataset exists.
+    bool sourceDependencyIsLocation = tester.dataSetHasStrValue(
+        DEPENDS_ON /*dataset name*/,
+        toNXPathString(transformationsPath) + "/" + LOCATION /*dataset value*/,
+        sourcePath /*where the dataset lives*/);
+    TS_ASSERT(sourceDependencyIsLocation);
+
+    // assert that the location depends on itself.
+    bool locationDependencyIsSelf = tester.hasAttributeInDataSet(
+        LOCATION /*dataset name*/, DEPENDS_ON /*dAttribute name*/,
+        NO_DEPENDENCY /*attribute value*/,
+        transformationsPath /*where the dataset lives*/);
+    TS_ASSERT(locationDependencyIsSelf);
+  }
+
+  void
+  test_when_both_orientation_and_Location_are_written_dependency_chain_is_orientation_location_self_dependent() {
+
+    // USING SOURCE FOR DEMONSTRATION.
+
+    /*
+        test scenario: saveInstrument called with non-zero rotation, and some
+        non-zero translation in source. Expected behaviour is: (dataset)
+       'depends_on' has value "/absoulute/path/to/orientation", (dataset)
+       'orientation' has dAttribute (AKA attribute of dataset) 'depends_on' with
+        value "/absoulute/path/to/location", and (dataset) 'location' has
+        dAttribute 'depends_on' with value "."
+         */
+
+    // Geometry for test instrument
+    const V3D detectorLocation(0, 0, 10);        // arbitrary
+    const V3D sourceLocation(0, 0, -10);         // arbitrary non-origin
+    const Quat sourceRotation(45, V3D(0, 1, 0)); // arbitrary non-zero
+
+    // create RAII file resource for testing
+    FileResource fileResource("both_transformations_dependency_test.hdf5");
+    std::string destinationFile = fileResource.fullPath();
+
+    // test instrument with non zero rotation and translation
+    auto instrument =
+        ComponentCreationHelper::createInstrumentWithSourceRotation(
+            sourceLocation, Mantid::Kernel::V3D(0, 0, 0), detectorLocation,
+            sourceRotation); // source rotation
+    auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
+
+    // path to NXtransformations subgoup in NXsource
+    FullNXPath transformationsPath = {
+        DEFAULT_ROOT_ENTRY_NAME, "test-instrument" /*instrument name*/,
+        "source" /*source name*/, TRANSFORMATIONS};
+
+    // path to NXsource group
+    FullNXPath sourcePath = transformationsPath;
+    sourcePath.pop_back(); // source path is one level abve transformationsPath
+
+    // call saveInstrument passing test instrument as parameter
+    NexusGeometrySave::saveInstrument(instr, destinationFile,
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
+
+    // test utility for checking output file
+    NexusFileReader tester(destinationFile);
+
+    // assert both location and orientation exists
+    bool hasLocation = tester.hasDataset(LOCATION, transformationsPath);
+    bool hasOrientation = tester.hasDataset(ORIENTATION, transformationsPath);
+    TS_ASSERT(hasOrientation); // assert orientation dataset exists.
+    TS_ASSERT(hasLocation);    // assert location dataset exists.
+
+    bool sourceDependencyIsOrientation =
+        tester.dataSetHasStrValue(DEPENDS_ON /*dataset name*/,
+                                  toNXPathString(transformationsPath) + "/" +
+                                      ORIENTATION /*value in dataset*/,
+                                  sourcePath /*where the dataset lives*/);
+    TS_ASSERT(sourceDependencyIsOrientation);
+    auto x = toNXPathString(transformationsPath) + "/" +
+             LOCATION /*dAttribute value*/;
+    bool orientationDependencyIsLocation = tester.hasAttributeInDataSet(
+        ORIENTATION /*dataset name*/, DEPENDS_ON /*dAttribute name*/,
+        toNXPathString(transformationsPath) + "/" +
+            LOCATION /*dAttribute value*/,
+        transformationsPath
+        /*where the dataset lives*/);
+    TS_ASSERT(orientationDependencyIsLocation);
+
+    bool locationDependencyIsSelf = tester.hasAttributeInDataSet(
+        LOCATION /*dataset name*/, DEPENDS_ON /*dAttribute name*/,
+        NO_DEPENDENCY /*dAttribute value*/,
+        transformationsPath /*where the dataset lives*/);
+    TS_ASSERT(locationDependencyIsSelf);
+  }
+
+  void
+  test_when_neither_orientation_nor_Location_are_written_dependency_is_self_and_nx_transformations_group_is_not_written() {
+
+    // USING SOURCE FOR DEMONSTRATION.
+
+    /*
+     test scenario: saveInstrument called with zero rotation, and
+     zero translation in source. Expected behaviour is: (dataset)
+     'depends_on' has value "."
+    */
+
+    const V3D detectorLocation(0, 0, 10);       // arbitrary
+    const V3D sourceLocation(0, 0, 0);          // set to zero
+    const Quat sourceRotation(0, V3D(0, 1, 0)); // set to zero
+
+    // create RAII file resource for testing
+    FileResource fileResource("neither_transformations_dependency_test.hdf5");
+    std::string destinationFile = fileResource.fullPath();
+
+    // test instrument with zero translation and rotation
+    auto instrument =
+        ComponentCreationHelper::createInstrumentWithSourceRotation(
+            sourceLocation, Mantid::Kernel::V3D(0, 0, 0), detectorLocation,
+            sourceRotation); // source rotation
+    auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
+
+    // path to NXtransformations subgoup in NXsource
+    FullNXPath transformationsPath = {
+        DEFAULT_ROOT_ENTRY_NAME, "test-instrument" /*instrument name*/,
+        "source" /*source name*/, TRANSFORMATIONS};
+
+    // path to NXsource group
+    FullNXPath sourcePath = transformationsPath;
+    sourcePath.pop_back(); // source path is one level abve transformationsPath
+
+    // call saveInstrument passing test instrument as parameter
+    NexusGeometrySave::saveInstrument(instr, destinationFile,
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
+
+    // test utility to check output file
+    NexusFileReader tester(destinationFile);
+
+    // assert source is self dependent
+    bool sourceDependencyIsSelf =
+        tester.dataSetHasStrValue(DEPENDS_ON, NO_DEPENDENCY, sourcePath);
+    TS_ASSERT(sourceDependencyIsSelf);
+
+    // assert the group NXtransformations doesnt exist in file
+    TS_ASSERT_THROWS(tester.openfullH5Path(transformationsPath),
+                     H5::GroupIException &);
+  }
+};
+
+#endif /* MANTID_NEXUSGEOMETRY_NEXUSGEOMETRYSAVETEST_H_ */