diff --git a/Framework/API/CMakeLists.txt b/Framework/API/CMakeLists.txt
index 05529388b2621945dceac437546e5087658091a4..5cf25c750d09a7725241e29a9765dc93c935e112 100644
--- a/Framework/API/CMakeLists.txt
+++ b/Framework/API/CMakeLists.txt
@@ -95,8 +95,8 @@ set ( SRC_FILES
 	src/LogFilterGenerator.cpp
 	src/LogManager.cpp
 	src/LogarithmScale.cpp
+	src/MDFrameValidator.cpp
 	src/MDGeometry.cpp
-        src/MDFrameValidator.cpp
 	src/MatrixWorkspace.cpp
 	src/MatrixWorkspaceMDIterator.cpp
 	src/ModeratorModel.cpp
@@ -111,7 +111,7 @@ set ( SRC_FILES
 	src/NullCoordTransform.cpp
 	src/NumericAxis.cpp
 	src/NumericAxisValidator.cpp
-        src/OrientedLatticeValidator.cpp
+	src/OrientedLatticeValidator.cpp
 	src/ParallelAlgorithm.cpp
 	src/ParamFunction.cpp
 	src/ParameterReference.cpp
@@ -144,6 +144,7 @@ set ( SRC_FILES
 	src/Workspace.cpp
 	src/WorkspaceFactory.cpp
 	src/WorkspaceGroup.cpp
+	src/WorkspaceHasDxValidator.cpp
 	src/WorkspaceHistory.cpp
 	src/WorkspaceNearestNeighbourInfo.cpp
 	src/WorkspaceNearestNeighbours.cpp
@@ -293,8 +294,8 @@ set ( INC_FILES
 	inc/MantidAPI/LogFilterGenerator.h
 	inc/MantidAPI/LogManager.h
 	inc/MantidAPI/LogarithmScale.h
+	inc/MantidAPI/MDFrameValidator.h
 	inc/MantidAPI/MDGeometry.h
-        inc/MantidAPI/MDFrameValidator.h
 	inc/MantidAPI/MatrixWorkspace.h
 	inc/MantidAPI/MatrixWorkspaceMDIterator.h
 	inc/MantidAPI/MatrixWorkspaceValidator.h
@@ -339,6 +340,8 @@ set ( INC_FILES
 	inc/MantidAPI/SpectraAxisValidator.h
 	inc/MantidAPI/SpectrumDetectorMapping.h
 	inc/MantidAPI/SpectrumInfo.h
+	inc/MantidAPI/SpectrumInfoItem.h
+	inc/MantidAPI/SpectrumInfoIterator.h
 	inc/MantidAPI/TableRow.h
 	inc/MantidAPI/TextAxis.h
 	inc/MantidAPI/TransformScaleFactory.h
@@ -348,6 +351,7 @@ set ( INC_FILES
 	inc/MantidAPI/WorkspaceFactory.h
 	inc/MantidAPI/WorkspaceGroup.h
 	inc/MantidAPI/WorkspaceGroup_fwd.h
+	inc/MantidAPI/WorkspaceHasDxValidator.h
 	inc/MantidAPI/WorkspaceHistory.h
 	inc/MantidAPI/WorkspaceNearestNeighbourInfo.h
 	inc/MantidAPI/WorkspaceNearestNeighbours.h
@@ -356,8 +360,6 @@ set ( INC_FILES
 	inc/MantidAPI/WorkspaceProperty.tcc
 	inc/MantidAPI/WorkspaceUnitValidator.h
 	inc/MantidAPI/Workspace_fwd.h
-	inc/MantidAPI/SpectrumInfoIterator.h
-	inc/MantidAPI/SpectrumInfoItem.h
 )
 
 set ( TEST_FILES
@@ -425,8 +427,8 @@ set ( TEST_FILES
 	LiveListenerTest.h
 	LogFilterGeneratorTest.h
 	LogManagerTest.h
-	MDGeometryTest.h
 	MDFrameValidatorTest.h
+	MDGeometryTest.h
 	MatrixWorkspaceMDIteratorTest.h
 	ModeratorModelTest.h
 	MuParserUtilsTest.h
@@ -465,6 +467,7 @@ set ( TEST_FILES
 	VectorParameterTest.h
 	WorkspaceFactoryTest.h
 	WorkspaceGroupTest.h
+	WorkspaceHasDxValidatorTest.h
 	WorkspaceHistoryIOTest.h
 	WorkspaceHistoryTest.h
 	WorkspaceNearestNeighbourInfoTest.h
diff --git a/Framework/API/inc/MantidAPI/WorkspaceHasDxValidator.h b/Framework/API/inc/MantidAPI/WorkspaceHasDxValidator.h
new file mode 100644
index 0000000000000000000000000000000000000000..e1d9570ac26bd9bcc6ed47797a43f31718589b1c
--- /dev/null
+++ b/Framework/API/inc/MantidAPI/WorkspaceHasDxValidator.h
@@ -0,0 +1,32 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef MANTID_API_WORKSPACEHASDXVALIDATOR_H_
+#define MANTID_API_WORKSPACEHASDXVALIDATOR_H_
+
+#include "MantidAPI/DllConfig.h"
+
+#include "MantidAPI/MatrixWorkspaceValidator.h"
+
+namespace Mantid {
+namespace API {
+
+/** WorkspaceHasDxValidator : A validator which checks that all histograms in a
+ * workspace have Dx values.
+ */
+class MANTID_API_DLL WorkspaceHasDxValidator final
+    : public MatrixWorkspaceValidator {
+public:
+  Kernel::IValidator_sptr clone() const override;
+
+private:
+  std::string checkValidity(MatrixWorkspace_sptr const &ws) const override;
+};
+
+} // namespace API
+} // namespace Mantid
+
+#endif /* MANTID_API_WORKSPACEHASDXVALIDATOR_H_ */
diff --git a/Framework/API/src/WorkspaceHasDxValidator.cpp b/Framework/API/src/WorkspaceHasDxValidator.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e2a061d80f875584556cc932edab206b7a379d58
--- /dev/null
+++ b/Framework/API/src/WorkspaceHasDxValidator.cpp
@@ -0,0 +1,24 @@
+#include "MantidAPI/WorkspaceHasDxValidator.h"
+
+namespace Mantid {
+namespace API {
+
+/// Return a deep clone of this validator.
+Kernel::IValidator_sptr WorkspaceHasDxValidator::clone() const {
+  return boost::make_shared<WorkspaceHasDxValidator>(*this);
+}
+
+/// Return an error string if not all histograms in ws have Dx, otherwise an
+/// empty string.
+std::string
+WorkspaceHasDxValidator::checkValidity(MatrixWorkspace_sptr const &ws) const {
+  for (size_t i = 0; i < ws->getNumberHistograms(); ++i) {
+    if (!ws->hasDx(i)) {
+      return "The workspace must have Dx values set";
+    }
+  }
+  return "";
+}
+
+} // namespace API
+} // namespace Mantid
diff --git a/Framework/API/test/WorkspaceHasDxValidatorTest.h b/Framework/API/test/WorkspaceHasDxValidatorTest.h
new file mode 100644
index 0000000000000000000000000000000000000000..7713fa5e0f213165f770af9feb627eed6c5fea3d
--- /dev/null
+++ b/Framework/API/test/WorkspaceHasDxValidatorTest.h
@@ -0,0 +1,45 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef MANTID_API_WORKSPACEHASDXVALIDATORTEST_H_
+#define MANTID_API_WORKSPACEHASDXVALIDATORTEST_H_
+
+#include <cxxtest/TestSuite.h>
+
+#include "MantidAPI/WorkspaceHasDxValidator.h"
+
+#include "MantidTestHelpers/FakeObjects.h"
+
+using namespace Mantid;
+
+class WorkspaceHasDxValidatorTest : public CxxTest::TestSuite {
+public:
+  // This pair of boilerplate methods prevent the suite being created statically
+  // This means the constructor isn't called when running other tests
+  static WorkspaceHasDxValidatorTest *createSuite() {
+    return new WorkspaceHasDxValidatorTest();
+  }
+  static void destroySuite(WorkspaceHasDxValidatorTest *suite) { delete suite; }
+
+  void test_returns_empty_string_for_valid_workspaces() {
+    auto ws = boost::make_shared<WorkspaceTester>();
+    ws->initialize(1, 1, 1);
+    auto dx = Kernel::make_cow<HistogramData::HistogramDx>(1, 0.);
+    ws->setSharedDx(0, std::move(dx));
+    WorkspaceHasDxValidator validator;
+    TS_ASSERT_EQUALS(validator.isValid(ws), "")
+  }
+
+  void test_returns_message_for_invalid_workspaces() {
+    auto ws = boost::make_shared<WorkspaceTester>();
+    ws->initialize(1, 1, 1);
+    WorkspaceHasDxValidator validator;
+    TS_ASSERT_EQUALS(validator.isValid(ws),
+                     "The workspace must have Dx values set")
+  }
+};
+
+#endif /* MANTID_API_WORKSPACEHASDXVALIDATORTEST_H_ */
diff --git a/Framework/Algorithms/CMakeLists.txt b/Framework/Algorithms/CMakeLists.txt
index 00b6f25d0e4e30b49aecf9a0fec1b12a90ce3aec..6514e828c709ee4ed3f41a3a853f1362a53525d9 100644
--- a/Framework/Algorithms/CMakeLists.txt
+++ b/Framework/Algorithms/CMakeLists.txt
@@ -160,6 +160,7 @@ set ( SRC_FILES
 	src/GetQsInQENSData.cpp
 	src/GetTimeSeriesLogInformation.cpp
 	src/GravitySANSHelper.cpp
+	src/GroupToXResolution.cpp
 	src/GroupWorkspaces.cpp
 	src/HRPDSlabCanAbsorption.cpp
 	src/He3TubeEfficiency.cpp
@@ -494,6 +495,7 @@ set ( INC_FILES
 	inc/MantidAlgorithms/GetQsInQENSData.h
 	inc/MantidAlgorithms/GetTimeSeriesLogInformation.h
 	inc/MantidAlgorithms/GravitySANSHelper.h
+	inc/MantidAlgorithms/GroupToXResolution.h
 	inc/MantidAlgorithms/GroupWorkspaces.h
 	inc/MantidAlgorithms/HRPDSlabCanAbsorption.h
 	inc/MantidAlgorithms/He3TubeEfficiency.h
@@ -701,7 +703,7 @@ set ( TEST_FILES
 	CalculateCarpenterSampleCorrectionTest.h
 	CalculateCountRateTest.h
 	CalculateDIFCTest.h
- 	CalculateDynamicRangeTest.h
+	CalculateDynamicRangeTest.h
 	CalculateEfficiencyTest.h
 	CalculateFlatBackgroundTest.h
 	CalculateIqtTest.h
@@ -835,6 +837,7 @@ set ( TEST_FILES
 	GetEiV1Test.h
 	GetQsInQENSDataTest.h
 	GetTimeSeriesLogInformationTest.h
+	GroupToXResolutionTest.h
 	GroupWorkspacesTest.h
 	HRPDSlabCanAbsorptionTest.h
 	He3TubeEfficiencyTest.h
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/GroupToXResolution.h b/Framework/Algorithms/inc/MantidAlgorithms/GroupToXResolution.h
new file mode 100644
index 0000000000000000000000000000000000000000..49a47e66f14134ef9e8c9ce659484945c4503e93
--- /dev/null
+++ b/Framework/Algorithms/inc/MantidAlgorithms/GroupToXResolution.h
@@ -0,0 +1,35 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef MANTID_ALGORITHMS_GROUPTOXRESOLUTION_H_
+#define MANTID_ALGORITHMS_GROUPTOXRESOLUTION_H_
+
+#include "MantidAPI/Algorithm.h"
+#include "MantidAlgorithms/DllConfig.h"
+
+namespace Mantid {
+namespace Algorithms {
+
+/** GroupToXResolution : Groups points within intervals defined by Dx into a
+ * single point.
+ */
+class MANTID_ALGORITHMS_DLL GroupToXResolution : public API::Algorithm {
+public:
+  const std::string name() const override;
+  int version() const override;
+  const std::string category() const override;
+  const std::string summary() const override;
+
+private:
+  void init() override;
+  void exec() override;
+  std::map<std::string, std::string> validateInputs() override;
+};
+
+} // namespace Algorithms
+} // namespace Mantid
+
+#endif /* MANTID_ALGORITHMS_GROUPTOXRESOLUTION_H_ */
diff --git a/Framework/Algorithms/src/GroupToXResolution.cpp b/Framework/Algorithms/src/GroupToXResolution.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7f763fa977d00b087664f5a8f05a43d591dad7fc
--- /dev/null
+++ b/Framework/Algorithms/src/GroupToXResolution.cpp
@@ -0,0 +1,160 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#include "MantidAlgorithms/GroupToXResolution.h"
+
+#include "MantidAPI/HistogramValidator.h"
+#include "MantidAPI/WorkspaceHasDxValidator.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
+#include "MantidHistogramData/HistogramBuilder.h"
+#include "MantidKernel/BoundedValidator.h"
+#include "MantidKernel/CompositeValidator.h"
+
+#include <boost/math/special_functions/pow.hpp>
+
+namespace {
+namespace Prop {
+std::string const FRACTION{"FractionOfDx"};
+std::string const INPUT_WS{"InputWorkspace"};
+std::string const OUTPUT_WS{"OutputWorkspace"};
+} // namespace Prop
+constexpr double FWHM_GAUSSIAN_EQUIVALENT{0.68};
+} // namespace
+
+namespace Mantid {
+namespace Algorithms {
+// Register the algorithm into the AlgorithmFactory
+DECLARE_ALGORITHM(GroupToXResolution)
+
+/// Algorithms name for identification. @see Algorithm::name
+const std::string GroupToXResolution::name() const {
+  return "GroupToXResolution";
+}
+
+/// Algorithm's version for identification. @see Algorithm::version
+int GroupToXResolution::version() const { return 1; }
+
+/// Algorithm's category for identification. @see Algorithm::category
+const std::string GroupToXResolution::category() const {
+  return "Transforms\\Rebin";
+}
+
+/// Algorithm's summary for use in the GUI and help. @see Algorithm::summary
+const std::string GroupToXResolution::summary() const {
+  return "Groups points within intervals given by the Dx into single points";
+}
+
+/** Initialize the algorithm's properties.
+ */
+void GroupToXResolution::init() {
+  auto inputValidator = boost::make_shared<Kernel::CompositeValidator>();
+  inputValidator->add(boost::make_shared<API::WorkspaceHasDxValidator>());
+  constexpr bool acceptHistograms{false};
+  inputValidator->add(
+      boost::make_shared<API::HistogramValidator>(acceptHistograms));
+  declareProperty(
+      Kernel::make_unique<API::WorkspaceProperty<>>(
+          Prop::INPUT_WS, "", Kernel::Direction::Input, inputValidator),
+      "An input workspace with Dx values.");
+  declareProperty(Kernel::make_unique<API::WorkspaceProperty<>>(
+                      Prop::OUTPUT_WS, "", Kernel::Direction::Output),
+                  "The grouped workspace.");
+  auto positive = boost::make_shared<Kernel::BoundedValidator<double>>();
+  positive->setLower(0.);
+  positive->setLowerExclusive(true);
+  declareProperty(Prop::FRACTION, 0.2, positive,
+                  "A fraction of Dx to group the points to.");
+}
+
+std::map<std::string, std::string> GroupToXResolution::validateInputs() {
+  std::map<std::string, std::string> issues;
+  API::MatrixWorkspace_const_sptr inWS = getProperty(Prop::INPUT_WS);
+  if (inWS->getNumberHistograms() != 1) {
+    issues[Prop::INPUT_WS] =
+        "The workspace should contain only a single histogram.";
+  }
+  return issues;
+}
+
+/** Execute the algorithm.
+ */
+void GroupToXResolution::exec() {
+  using boost::math::pow;
+  API::MatrixWorkspace_const_sptr inWS = getProperty(Prop::INPUT_WS);
+  double const groupingFraction = getProperty(Prop::FRACTION);
+  HistogramData::Histogram h(HistogramData::Histogram::XMode::Points,
+                             HistogramData::Histogram::YMode::Counts);
+  auto const &inXs = inWS->x(0);
+  auto const &inYs = inWS->y(0);
+  auto const &inEs = inWS->e(0);
+  auto const &inDxs = inWS->dx(0);
+  std::vector<double> outXs;
+  outXs.reserve(inXs.size());
+  std::vector<double> outYs;
+  outYs.reserve(inXs.size());
+  std::vector<double> outEs;
+  outEs.reserve(inXs.size());
+  std::vector<double> outDxs;
+  outDxs.reserve(inXs.size());
+  size_t pointIndex{0};
+  double begin = inXs.front();
+  while (true) {
+    auto const Dx = inDxs[pointIndex];
+    if (Dx <= 0.) {
+      throw std::out_of_range("Nonpositive DX value in the workspace.");
+    }
+    auto const width = groupingFraction * Dx;
+    auto const end = inXs[pointIndex] + width;
+    auto const beginXIterator =
+        std::lower_bound(inXs.cbegin(), inXs.cend(), begin);
+    auto const endXIterator = std::lower_bound(inXs.cbegin(), inXs.cend(), end);
+    auto const pickSize =
+        static_cast<size_t>(std::distance(beginXIterator, endXIterator));
+    if (pickSize > 0) {
+      auto const offset =
+          static_cast<size_t>(std::distance(inXs.cbegin(), beginXIterator));
+      double xSum{0.};
+      double ySum{0.};
+      double eSquaredSum{0.};
+      for (size_t pickIndex = offset; pickIndex < offset + pickSize;
+           ++pickIndex) {
+        xSum += inXs[pickIndex];
+        ySum += inYs[pickIndex];
+        eSquaredSum += pow<2>(inEs[pickIndex]);
+      }
+      outXs.emplace_back(xSum / static_cast<double>(pickSize));
+      outYs.emplace_back(ySum / static_cast<double>(pickSize));
+      outEs.emplace_back(std::sqrt(eSquaredSum) /
+                         static_cast<double>(pickSize));
+      auto const groupedXWidth = *std::prev(endXIterator) - *beginXIterator;
+      outDxs.emplace_back(
+          std::sqrt(pow<2>(inDxs[pointIndex]) +
+                    pow<2>(FWHM_GAUSSIAN_EQUIVALENT * groupedXWidth)));
+    } else {
+      throw std::out_of_range(
+          "Failed to group. Is the X data sorted in ascending order?");
+    }
+    begin = end;
+    if (begin > inXs.back()) {
+      break;
+    }
+    pointIndex += pickSize;
+  }
+  HistogramData::HistogramBuilder constructionYard;
+  constructionYard.setX(std::move(outXs));
+  constructionYard.setY(std::move(outYs));
+  constructionYard.setE(std::move(outEs));
+  constructionYard.setDx(std::move(outDxs));
+  constructionYard.setDistribution(false);
+  API::MatrixWorkspace_sptr outWS =
+      DataObjects::create<DataObjects::Workspace2D>(*inWS,
+                                                    constructionYard.build());
+  setProperty(Prop::OUTPUT_WS, outWS);
+}
+
+} // namespace Algorithms
+} // namespace Mantid
diff --git a/Framework/Algorithms/test/GroupToXResolutionTest.h b/Framework/Algorithms/test/GroupToXResolutionTest.h
new file mode 100644
index 0000000000000000000000000000000000000000..3e990f78fe020977597f6223f623e0e2792aea5f
--- /dev/null
+++ b/Framework/Algorithms/test/GroupToXResolutionTest.h
@@ -0,0 +1,240 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef MANTID_ALGORITHMS_GROUPTOXRESOLUTIONTEST_H_
+#define MANTID_ALGORITHMS_GROUPTOXRESOLUTIONTEST_H_
+
+#include <cxxtest/TestSuite.h>
+
+#include "MantidAlgorithms/GroupToXResolution.h"
+
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
+#include "MantidHistogramData/LinearGenerator.h"
+#include "MantidHistogramData/QuadraticGenerator.h"
+
+#include <boost/math/special_functions/pow.hpp>
+
+using namespace Mantid;
+using boost::math::pow;
+
+class GroupToXResolutionTest : public CxxTest::TestSuite {
+public:
+  // This pair of boilerplate methods prevent the suite being created statically
+  // This means the constructor isn't called when running other tests
+  static GroupToXResolutionTest *createSuite() {
+    return new GroupToXResolutionTest();
+  }
+  static void destroySuite(GroupToXResolutionTest *suite) { delete suite; }
+
+  void test_Init() {
+    Algorithms::GroupToXResolution alg;
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT(alg.isInitialized())
+  }
+
+  void test_single_point_remains_unchanged() {
+    HistogramData::Points Xs{0.23};
+    HistogramData::Counts Ys{1.42};
+    HistogramData::Histogram h(Xs, Ys);
+    API::MatrixWorkspace_sptr inputWS =
+        DataObjects::create<DataObjects::Workspace2D>(1, std::move(h));
+    auto Dxs = Kernel::make_cow<HistogramData::HistogramDx>(1, 1.);
+    inputWS->setSharedDx(0, std::move(Dxs));
+    Algorithms::GroupToXResolution alg;
+    alg.setChild(true);
+    alg.setRethrows(true);
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("InputWorkspace", inputWS))
+    TS_ASSERT_THROWS_NOTHING(
+        alg.setPropertyValue("OutputWorkspace", "_unused_for_child"))
+    TS_ASSERT_THROWS_NOTHING(alg.execute())
+    TS_ASSERT(alg.isExecuted())
+    API::MatrixWorkspace_sptr outputWS = alg.getProperty("OutputWorkspace");
+    TS_ASSERT(outputWS)
+    TS_ASSERT_EQUALS(outputWS->getNumberHistograms(), 1)
+    TS_ASSERT_EQUALS(outputWS->blocksize(), 1)
+    TS_ASSERT_EQUALS(outputWS->x(0).front(), 0.23)
+    TS_ASSERT_EQUALS(outputWS->y(0).front(), 1.42)
+    TS_ASSERT_EQUALS(outputWS->e(0).front(), std::sqrt(1.42))
+    TS_ASSERT(outputWS->hasDx(0))
+    TS_ASSERT_EQUALS(outputWS->dx(0).front(), 1.)
+  }
+
+  void test_two_points_get_averaged() {
+    HistogramData::Points Xs{0.2, 0.6};
+    HistogramData::Counts Ys{1.5, 2.5};
+    HistogramData::CountStandardDeviations Es{2., 3.};
+    HistogramData::Histogram h(Xs, Ys, Es);
+    API::MatrixWorkspace_sptr inputWS =
+        DataObjects::create<DataObjects::Workspace2D>(1, std::move(h));
+    auto Dxs = Kernel::make_cow<HistogramData::HistogramDx>(2);
+    {
+      auto &DxData = Dxs.access();
+      DxData.front() = 1.2;
+      DxData.back() = 1.7;
+    }
+    inputWS->setSharedDx(0, std::move(Dxs));
+    Algorithms::GroupToXResolution alg;
+    alg.setChild(true);
+    alg.setRethrows(true);
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("InputWorkspace", inputWS))
+    TS_ASSERT_THROWS_NOTHING(
+        alg.setPropertyValue("OutputWorkspace", "_unused_for_child"))
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("FractionOfDx", 1.))
+    TS_ASSERT_THROWS_NOTHING(alg.execute())
+    TS_ASSERT(alg.isExecuted())
+    API::MatrixWorkspace_sptr outputWS = alg.getProperty("OutputWorkspace");
+    TS_ASSERT(outputWS)
+    TS_ASSERT_EQUALS(outputWS->getNumberHistograms(), 1)
+    TS_ASSERT_EQUALS(outputWS->blocksize(), 1)
+    TS_ASSERT_EQUALS(outputWS->x(0).front(), (0.2 + 0.6) / 2.)
+    TS_ASSERT_EQUALS(outputWS->y(0).front(), (1.5 + 2.5) / 2.)
+    TS_ASSERT_EQUALS(outputWS->e(0).front(),
+                     std::sqrt(pow<2>(2.) + pow<2>(3.)) / 2.)
+    TS_ASSERT(outputWS->hasDx(0))
+    TS_ASSERT_EQUALS(outputWS->dx(0).front(),
+                     std::sqrt(pow<2>(1.2) + pow<2>(0.68 * (0.6 - 0.2))))
+  }
+
+  void test_two_spearate_points_remain_unchanged() {
+    HistogramData::Points Xs{0.2, 0.6};
+    HistogramData::Counts Ys{1.5, 2.5};
+    HistogramData::CountStandardDeviations Es{2., 3.};
+    HistogramData::Histogram h(Xs, Ys, Es);
+    API::MatrixWorkspace_sptr inputWS =
+        DataObjects::create<DataObjects::Workspace2D>(1, std::move(h));
+    auto Dxs = Kernel::make_cow<HistogramData::HistogramDx>(2);
+    {
+      auto &DxData = Dxs.access();
+      DxData.front() = 0.1;
+      DxData.back() = 0.3;
+    }
+    inputWS->setSharedDx(0, std::move(Dxs));
+    Algorithms::GroupToXResolution alg;
+    alg.setChild(true);
+    alg.setRethrows(true);
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("InputWorkspace", inputWS))
+    TS_ASSERT_THROWS_NOTHING(
+        alg.setPropertyValue("OutputWorkspace", "_unused_for_child"))
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("FractionOfDx", 1.))
+    TS_ASSERT_THROWS_NOTHING(alg.execute())
+    TS_ASSERT(alg.isExecuted())
+    API::MatrixWorkspace_sptr outputWS = alg.getProperty("OutputWorkspace");
+    TS_ASSERT(outputWS)
+    TS_ASSERT_EQUALS(outputWS->getNumberHistograms(), 1)
+    TS_ASSERT_EQUALS(outputWS->blocksize(), 2)
+    TS_ASSERT_EQUALS(outputWS->x(0).front(), 0.2)
+    TS_ASSERT_EQUALS(outputWS->y(0).front(), 1.5)
+    TS_ASSERT_EQUALS(outputWS->e(0).front(), 2.)
+    TS_ASSERT(outputWS->hasDx(0))
+    TS_ASSERT_EQUALS(outputWS->dx(0).front(), 0.1)
+    TS_ASSERT_EQUALS(outputWS->x(0).back(), 0.6)
+    TS_ASSERT_EQUALS(outputWS->y(0).back(), 2.5)
+    TS_ASSERT_EQUALS(outputWS->e(0).back(), 3.)
+    TS_ASSERT_EQUALS(outputWS->dx(0).back(), 0.3)
+  }
+
+  void test_four_points_grouped_into_two() {
+    HistogramData::Points Xs{0.2, 0.6, 5.1, 5.7};
+    HistogramData::Counts Ys{1.5, 2.5, -2.5, -1.5};
+    HistogramData::CountStandardDeviations Es{2., 3., 2.5, 1.5};
+    HistogramData::Histogram h(Xs, Ys, Es);
+    API::MatrixWorkspace_sptr inputWS =
+        DataObjects::create<DataObjects::Workspace2D>(1, std::move(h));
+    auto Dxs = Kernel::make_cow<HistogramData::HistogramDx>(4);
+    {
+      auto &DxData = Dxs.access();
+      DxData = {1., 0.1, 2., 0.2};
+    }
+    inputWS->setSharedDx(0, std::move(Dxs));
+    Algorithms::GroupToXResolution alg;
+    alg.setChild(true);
+    alg.setRethrows(true);
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("InputWorkspace", inputWS))
+    TS_ASSERT_THROWS_NOTHING(
+        alg.setPropertyValue("OutputWorkspace", "_unused_for_child"))
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("FractionOfDx", 1.))
+    TS_ASSERT_THROWS_NOTHING(alg.execute())
+    TS_ASSERT(alg.isExecuted())
+    API::MatrixWorkspace_sptr outputWS = alg.getProperty("OutputWorkspace");
+    TS_ASSERT(outputWS)
+    TS_ASSERT_EQUALS(outputWS->getNumberHistograms(), 1)
+    TS_ASSERT_EQUALS(outputWS->blocksize(), 2)
+    TS_ASSERT_EQUALS(outputWS->x(0).front(), (0.2 + 0.6) / 2.)
+    TS_ASSERT_EQUALS(outputWS->y(0).front(), (1.5 + 2.5) / 2.)
+    TS_ASSERT_EQUALS(outputWS->e(0).front(),
+                     std::sqrt(pow<2>(2.) + pow<2>(3.)) / 2.)
+    TS_ASSERT(outputWS->hasDx(0))
+    TS_ASSERT_EQUALS(outputWS->dx(0).front(),
+                     std::sqrt(pow<2>(1.) + pow<2>(0.68 * (0.6 - 0.2))))
+    TS_ASSERT_EQUALS(outputWS->x(0).back(), (5.1 + 5.7) / 2.)
+    TS_ASSERT_EQUALS(outputWS->y(0).back(), (-2.5 + -1.5) / 2)
+    TS_ASSERT_EQUALS(outputWS->e(0).back(),
+                     std::sqrt(pow<2>(2.5) + pow<2>(1.5)) / 2.)
+    TS_ASSERT_EQUALS(outputWS->dx(0).back(),
+                     std::sqrt(pow<2>(2.) + pow<2>(0.68 * (5.7 - 5.1))))
+  }
+};
+
+class GroupToXResolutionTestPerformance : public CxxTest::TestSuite {
+public:
+  // This pair of boilerplate methods prevent the suite being created statically
+  // This means the constructor isn't called when running other tests
+  static GroupToXResolutionTestPerformance *createSuite() {
+    return new GroupToXResolutionTestPerformance();
+  }
+  static void destroySuite(GroupToXResolutionTestPerformance *suite) {
+    delete suite;
+  }
+
+  GroupToXResolutionTestPerformance() : m_alg() {
+    m_alg.setRethrows(true);
+    m_alg.setChild(true);
+    m_alg.initialize();
+  }
+
+  void setUp() override {
+    constexpr double xZeroth{0.};
+    constexpr double xFirst{0.};
+    constexpr double xSecond{0.4};
+    constexpr size_t n{10000};
+    HistogramData::Points Xs(
+        n, HistogramData::QuadraticGenerator(xZeroth, xFirst, xSecond));
+    HistogramData::Counts Ys(n, 1.3);
+    HistogramData::CountStandardDeviations Es(n, 1.1);
+    HistogramData::Histogram h(Xs, Ys, Es);
+    API::MatrixWorkspace_sptr inputWS =
+        DataObjects::create<DataObjects::Workspace2D>(1, std::move(h));
+    // Construct DX such that in the beginning, we group multiple points
+    // and after a crossover, no grouping happens.
+    constexpr double initialGroupSize{10.};
+    constexpr double crossover{0.8 * n};
+    constexpr double dxZeroth{2. * initialGroupSize * xSecond};
+    constexpr double dxFirst{(2. * crossover - 2. * initialGroupSize + 1.) /
+                             crossover * xSecond};
+    auto Dxs = Kernel::make_cow<HistogramData::HistogramDx>(
+        n, HistogramData::LinearGenerator(dxZeroth, dxFirst));
+    inputWS->setSharedDx(0, std::move(Dxs));
+    m_alg.setProperty("InputWorkspace", inputWS);
+    m_alg.setProperty("OutputWorkspace", "_out");
+    m_alg.setProperty("FractionOfDx", 1.);
+  }
+
+  void test_performance() {
+    for (int i = 0; i < 5000; ++i) {
+      TS_ASSERT_THROWS_NOTHING(m_alg.execute())
+    }
+  }
+
+private:
+  Algorithms::GroupToXResolution m_alg;
+};
+
+#endif /* MANTID_ALGORITHMS_GROUPTOXRESOLUTIONTEST_H_ */
diff --git a/Framework/HistogramData/inc/MantidHistogramData/Histogram.h b/Framework/HistogramData/inc/MantidHistogramData/Histogram.h
index 789c58738a79daa0651da76135d2e9f6ed441eaa..af0605e4aa9d69589c7599a6b7c1cd272c2ee900 100644
--- a/Framework/HistogramData/inc/MantidHistogramData/Histogram.h
+++ b/Framework/HistogramData/inc/MantidHistogramData/Histogram.h
@@ -197,8 +197,6 @@ private:
   template <class... T> bool selfAssignmentDx(const T &...) { return false; }
   template <class... T> bool selfAssignmentY(const T &...) { return false; }
   template <class... T> bool selfAssignmentE(const T &...) { return false; }
-  void switchDxToBinEdges();
-  void switchDxToPoints();
 
   XMode m_xMode;
   YMode m_yMode{YMode::Uninitialized};
diff --git a/docs/source/algorithms/GroupToXResolution-v1.rst b/docs/source/algorithms/GroupToXResolution-v1.rst
new file mode 100644
index 0000000000000000000000000000000000000000..88afc513c9c77c31291ffbf77248da9c33b69af0
--- /dev/null
+++ b/docs/source/algorithms/GroupToXResolution-v1.rst
@@ -0,0 +1,62 @@
+
+.. algorithm::
+
+.. summary::
+
+.. relatedalgorithms::
+
+.. properties::
+
+Description
+-----------
+
+This algorithm groups the points of a single histogram workspace according to the X resolution stored in the DX array.
+
+The figure below shows schematically how the grouping procedure proceeds.
+
+.. figure:: ../images/GroupToXResolution_grouping_schema.png
+   :alt: Schematic image of the point grouping algorithm.
+   :scale: 100%
+
+#. Select first ungrouped point :math:`i`. The X resolution (DX) for this point is :math:`D_{i}`.
+#. Calculate grouping width :math:`w_{i} =` ``FractionOfDx`` * :math:`D_{i}`.
+#. Select ungrouped points within :math:`w_{i}` from point :math:`i`.
+#. Calculate the average X, average Y, and the square root of the averaged squared sums of E of the selected points.
+#. Calculate new resolution :math:`D^{\circ} = \sqrt{D_{i}^{2} + (0.68 \Delta X)^{2}}` where :math:`\Delta X` is span of the X values of the selected points.
+#. Replace the selected points with a single grouped point.
+#. Return to 1.
+
+Usage
+-----
+
+**Example - Grouping points to X resolution**
+
+.. plot::
+   :include-source:
+
+   from mantid.simpleapi import CreateWorkspace, DeleteWorkspaces, GroupToXResolution
+   import matplotlib.pyplot as plt
+   import numpy as np
+   # Create a workspace with exponential decay.
+   Xs = np.arange(0.01, 5., 0.01)
+   Ys = np.exp(-Xs)
+   # A clumsy way for filling a numpy array.
+   # Numpy version > 1.7 would support 'DXs = full_like(Ys, 1.)'
+   DXs = np.empty_like(Ys)
+   DXs.fill(1.)
+   original = CreateWorkspace(Xs, Ys, Dx=DXs, NSpec=1)
+   grouped = GroupToXResolution(original)
+   # Plot side-by-side comparison.
+   fig, (left, right) = plt.subplots(ncols=2, subplot_kw={'projection':'mantid'})
+   left.errorbar(original, linestyle='None')
+   left.set_title('Original')
+   right.errorbar(grouped, linestyle='None')
+   right.set_title('Grouped')
+   # Uncomment the next line to show the plot window.
+   #fig.show()
+   DeleteWorkspaces(['original', 'grouped'])
+
+.. categories::
+
+.. sourcelink::
+
diff --git a/docs/source/images/GroupToXResolution_grouping_schema.png b/docs/source/images/GroupToXResolution_grouping_schema.png
new file mode 100644
index 0000000000000000000000000000000000000000..6f1df6d60bbebf1b1799af0c96d5539707b4dea0
Binary files /dev/null and b/docs/source/images/GroupToXResolution_grouping_schema.png differ
diff --git a/docs/source/release/v3.14.0/reflectometry.rst b/docs/source/release/v3.14.0/reflectometry.rst
index df356e4fc187062fe9677aa13b56fc4843a0c06f..b7473f39939dec96b5ce2b3de4c124eb42a0dc79 100644
--- a/docs/source/release/v3.14.0/reflectometry.rst
+++ b/docs/source/release/v3.14.0/reflectometry.rst
@@ -5,20 +5,6 @@ Reflectometry Changes
 .. contents:: Table of Contents
    :local:
 
-
-ISIS Reflectometry Interface
-----------------------------
-
-New
-###
-
-* ``SaveReflectometryAscii`` is a general algorithm which saves the first spectrum of a workspace in Ascii format particularly suited for reflectometry data.
-
-Improvements
-############
-
-- The four Ascii save algorithms ``SaveANSTOAscii``, ``SaveILLCosmosAscii``, ``SaveReflCustomAscii`` and ``SaveReflThreeColumnAscii`` now correctly save x-error and can treat correctly point data and histograms. They are, however, deprecated in favour of ``SaveReflectometryAscii``. Please see ``SaveReflectometryAscii`` for more documentation.
-
 Algorithms
 ----------
 
@@ -29,18 +15,31 @@ New
 - Added algorithm :ref:`algm-ApplyFloodWorkspace` which applies flood corrections to a workspace.
 - :ref:`FindReflectometryLines <algm-FindReflectometryLines-v2>` has been rewritten and updated to version 2. The new version finds a single line by a Gaussian fit. Version 1 has been deprecated and will be removed in a future release.
 - Added algorithm :ref:`algm-ReflectometrySliceEventWorkspace` which slices an input event workspace into multiple slices, producing a histogram workspace suitable for use with :ref:`algm-ReflectometryReductionOneAuto`.
+- :ref:`algm-SaveReflectometryAscii` is a general algorithm which saves the first spectrum of a workspace in Ascii format particularly suited for reflectometry data.
+- Some computations from :ref:`algm-ReflectometryMomentumTransfer` were extracted to a new algorithm, :ref:`algm-ReflectometryBeamStatistics`.
+- :ref:`algm-GroupToXResolution` can be used to group the reflectivity data (as point data) to the :math`Q_z` resolution.
 
 Improved
 ########
 
 - The ILL reduction workflow algorithms were reorganized to allow correct reflectivity calculation in the :literal:`SumInLambda` case.
 - Added flood corrections to :ref:`ReflectometryReductionOneAuto <algm-ReflectometryReductionOneAuto-v2>`. The correction data can be provided either via a flood workspace passed as a property or taken from the parameter file.
+- The four Ascii save algorithms :ref:`algm-SaveANSTOAscii`, :ref:`algm-SaveILLCosmosAscii`, :ref:`algm-SaveReflCustomAscii` and :ref:`algm-SaveReflThreeColumnAscii` now correctly save x-error and can treat correctly point data and histograms. They are, however, deprecated in favour of :ref:`algm-SaveReflectometryAscii`. Please see :ref:`algm-SaveReflectometryAscii` for more documentation.
+- :ref:`algm-ReflectometryReductionOneAuto` now supports the Wildes method for polarization corrections as well as Fredrikze when configured in the parameters file.
+- :ref:`algm-ReflectometryReductionOne`, :ref:`algm-ReflectometryReductionOneAuto`, :ref:`algm-CreateTransmissionWorkspace` and :ref:`algm-CreateTransmissionWorkspaceAuto` now use spectrum numbers for their processing instructions instead of workspace indcies
+- :ref:`algm-ReflectometryReductionOne` and :ref:`algm-ReflectometryReductionOneAuto` Now take a parameter to pass processing instructions to the transmission workspace algorithms and no longer accept strict spectrum checking
+- Common naming of slit component name and size properties across algorithms.
+- :ref:`algm-SpecularReflectionPositionCorrect` is now compatible with the reflectometers at ILL.
+- :ref:`algm-CreateTransmissionWorkspace` and :ref:`algm-CreateTransmissionWorkspaceAuto` now use NormalizeByIntegratedMontitors instead of using MonitorIntegrationWavelengthMin and MonitorIntegrationWavelengthMax being defined, to determine how to normalize. 
 
 Bug fixes
 #########
 
 - Fixed the error propagation in :math:`Q` grouping in :ref:`ReflectometryILLConvertToQ <algm-ReflectometryILLConvertToQ>`
 - Handling of group workspaces containing single workspaces when scaling by period and using :literal:`ScaleFactorFromPeriod`, i.e. :literal:`UseManualScaleFactors` is true, :literal:`ManualScaleFactors` remains empty.
+- A bug has been fixed on the Settings tab where the IncludePartialBins check box had been hidden by a misplaced text entry box.
+- :ref:`algm-ReflectometryReductionOneAuto` No longer sums all of a transmission run's workspaces and instead will use the first run only
+- In :ref:`algm-ReflectometryReductionOneAuto` an issue where if you gave only one of either MomentumTransferMax or MomentumTransferMin were specified it would be ignored, this has been fixed.
 
 Liquids Reflectometer
 ---------------------
@@ -70,30 +69,4 @@ Bug fixes
 
 - The SaveASCII tab from the interface was unable to save in some places on Windows and that has now been fixed.
 
-Algorithms
-----------
-
-
-New
-###
-
-- Some computations from :ref:`algm-ReflectometryMomentumTransfer` were extracted to a new algorithm, :ref:`algm-ReflectometryBeamStatistics`.
-
-Improved
-########
-
-- :ref:`algm-ReflectometryReductionOneAuto` now supports the Wildes method for polarization corrections as well as Fredrikze when configured in the parameters file.
-- :ref:`algm-ReflectometryReductionOne`, :ref:`algm-ReflectometryReductionOneAuto`, :ref:`algm-CreateTransmissionWorkspace` and :ref:`algm-CreateTransmissionWorkspaceAuto` now use spectrum numbers for their processing instructions instead of workspace indcies
-- :ref:`algm-ReflectometryReductionOne` and :ref:`algm-ReflectometryReductionOneAuto` Now take a parameter to pass processing instructions to the transmission workspace algorithms and no longer accept strict spectrum checking
-- Common naming of slit component name and size properties across algorithms.
-- :ref:`algm-SpecularReflectionPositionCorrect` is now compatible with the reflectometers at ILL.
-- :ref:`algm-CreateTransmissionWorkspace` and :ref:`algm-CreateTransmissionWorkspaceAuto` now use NormalizeByIntegratedMontitors instead of using MonitorIntegrationWavelengthMin and MonitorIntegrationWavelengthMax being defined, to determine how to normalize. 
-
-Bug fixes
-#########
-
-- A bug has been fixed on the Settings tab where the IncludePartialBins check box had been hidden by a misplaced text entry box.
-- :ref:`algm-ReflectometryReductionOneAuto` No longer sums all of a transmission run's workspaces and instead will use the first run only
-- In :ref:`algm-ReflectometryReductionOneAuto` an issue where if you gave only one of either MomentumTransferMax or MomentumTransferMin were specified it would be ignored, this has been fixed.
-
 :ref:`Release 3.14.0 <v3.14.0>`