diff --git a/Framework/Crystal/inc/MantidCrystal/PeakHKLErrors.h b/Framework/Crystal/inc/MantidCrystal/PeakHKLErrors.h
index 5d06df47752bebcc0526fb2e80191e2781596526..b4f7577491ad34528c464e34ad28579450976a58 100644
--- a/Framework/Crystal/inc/MantidCrystal/PeakHKLErrors.h
+++ b/Framework/Crystal/inc/MantidCrystal/PeakHKLErrors.h
@@ -153,6 +153,11 @@ private:
                 // OptRuns setup
 
   void setUpOptRuns();
+
+  mutable boost::shared_ptr<Geometry::Instrument> instChange;
+  mutable bool hasParameterMap = false;
+  mutable Kernel::V3D sampPos;
+  mutable boost::shared_ptr<const Geometry::ParameterMap> pmapSv;
 };
 } // namespace Crystal
 } // namespace Mantid
diff --git a/Framework/Crystal/src/FilterPeaks.cpp b/Framework/Crystal/src/FilterPeaks.cpp
index 3fa0520ff9b3a8216bfb444bc72a2788dd22b1d5..b1b1b7aef61de38d16b3bbdb6103e971ab154b5f 100644
--- a/Framework/Crystal/src/FilterPeaks.cpp
+++ b/Framework/Crystal/src/FilterPeaks.cpp
@@ -34,6 +34,8 @@ double QMOD(const Mantid::Geometry::IPeak &p) {
 double SN(const Mantid::Geometry::IPeak &p) {
   return p.getIntensity() / p.getSigmaIntensity();
 }
+
+double RUN(const Mantid::Geometry::IPeak &p) { return p.getRunNumber(); }
 } // namespace
 
 namespace Mantid {
@@ -66,7 +68,7 @@ void FilterPeaks::init() {
 
   std::vector<std::string> filters{"h+k+l",        "h^2+k^2+l^2", "Intensity",
                                    "Signal/Noise", "QMod",        "Wavelength",
-                                   "DSpacing",     "TOF"};
+                                   "DSpacing",     "TOF",         "RunNumber"};
   declareProperty("FilterVariable", "",
                   boost::make_shared<StringListValidator>(filters),
                   "The variable on which to filter the peaks");
@@ -150,6 +152,8 @@ FilterPeaks::FilterFunction FilterPeaks::getFilterVariableFunction(
     filterFunction = &SN;
   else if (filterVariable == "QMod")
     filterFunction = &QMOD;
+  else if (filterVariable == "RunNumber")
+    filterFunction = &RUN;
   else
     throw std::invalid_argument("Unknown FilterVariable: " + filterVariable);
   return filterFunction;
diff --git a/Framework/Crystal/src/PeakHKLErrors.cpp b/Framework/Crystal/src/PeakHKLErrors.cpp
index 3edfe24e37f5f5fc0a06130cca41282b3975c7ae..f7a32da50a7c93b372562836a1d15ee8ebc22294 100644
--- a/Framework/Crystal/src/PeakHKLErrors.cpp
+++ b/Framework/Crystal/src/PeakHKLErrors.cpp
@@ -190,26 +190,31 @@ boost::shared_ptr<Geometry::Instrument>
 PeakHKLErrors::getNewInstrument(PeaksWorkspace_sptr Peaks) const {
   Geometry::Instrument_const_sptr instSave = Peaks->getPeak(0).getInstrument();
   auto pmap = boost::make_shared<Geometry::ParameterMap>();
-  boost::shared_ptr<const Geometry::ParameterMap> pmapSv =
-      instSave->getParameterMap();
 
   if (!instSave) {
     g_log.error(" Peaks workspace does not have an instrument");
     throw std::invalid_argument(" Not all peaks have an instrument");
   }
-  auto instChange = boost::shared_ptr<Geometry::Instrument>();
 
-  if (!instSave->isParametrized()) {
-
-    boost::shared_ptr<Geometry::Instrument> instClone(instSave->clone());
-    auto Pinsta = boost::make_shared<Geometry::Instrument>(instSave, pmap);
-
-    instChange = Pinsta;
-  } else // catch(... )
-  {
-    auto P1 = boost::make_shared<Geometry::Instrument>(
-        instSave->baseInstrument(), instSave->makeLegacyParameterMap());
-    instChange = P1;
+  if (!hasParameterMap) {
+    pmapSv = instSave->getParameterMap();
+    hasParameterMap = true;
+    if (!instSave->isParametrized()) {
+
+      boost::shared_ptr<Geometry::Instrument> instClone(instSave->clone());
+      auto Pinsta = boost::make_shared<Geometry::Instrument>(instSave, pmap);
+
+      instChange = Pinsta;
+      IComponent_const_sptr sample = instChange->getSample();
+      sampPos = sample->getRelativePos();
+    } else // catch(... )
+    {
+      auto P1 = boost::make_shared<Geometry::Instrument>(
+          instSave->baseInstrument(), instSave->makeLegacyParameterMap());
+      instChange = P1;
+      IComponent_const_sptr sample = instChange->getSample();
+      sampPos = sample->getRelativePos();
+    }
   }
 
   if (!instChange) {
@@ -219,11 +224,10 @@ PeakHKLErrors::getNewInstrument(PeaksWorkspace_sptr Peaks) const {
   //------------------"clone" orig instruments pmap -------------------
 
   cLone(pmap, instSave, pmapSv);
-  IComponent_const_sptr sample = instChange->getSample();
-  V3D sampPos = sample->getRelativePos();
   V3D sampOffsets(getParameter("SampleXOffset"), getParameter("SampleYOffset"),
                   getParameter("SampleZOffset"));
 
+  IComponent_const_sptr sample = instChange->getSample();
   pmap->addPositionCoordinate(sample.get(), std::string("x"),
                               sampPos.X() + sampOffsets.X());
   pmap->addPositionCoordinate(sample.get(), std::string("y"),
diff --git a/Framework/PythonInterface/plugins/algorithms/OptimizeCrystalPlacementByRun.py b/Framework/PythonInterface/plugins/algorithms/OptimizeCrystalPlacementByRun.py
new file mode 100644
index 0000000000000000000000000000000000000000..0ab0361b4cae86e3c2e6443e5433ed6fb64d5f4a
--- /dev/null
+++ b/Framework/PythonInterface/plugins/algorithms/OptimizeCrystalPlacementByRun.py
@@ -0,0 +1,76 @@
+# 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 +
+#pylint: disable=no-init
+
+from mantid.api import PythonAlgorithm, AlgorithmFactory, ITableWorkspaceProperty
+from mantid.kernel import Direction
+from mantid.simpleapi import *
+from mantid import mtd
+
+# Create an empty table workspace to be populated by a python script.
+
+
+class OptimizeCrystalPlacementByRun(PythonAlgorithm):
+
+    def summary(self):
+        return "Optimizes the sample position for each run in a peaks workspace."
+
+    def category(self):
+        return 'Crystal\\Corrections'
+
+    def seeAlso(self):
+        return [ "OptimizeCrystalPlacement" ]
+
+    def PyInit(self):
+        # Declare properties
+        self.declareProperty(ITableWorkspaceProperty("InputWorkspace", "", Direction.Input),
+                             "The name of the peaks workspace that will be optimized.")
+        self.declareProperty("Tolerance", 0.15, "Tolerance of indexing of peaks.")
+        self.declareProperty("OutputWorkspace", "",
+                             "The name of the peaks workspace that will be created.")
+
+    def PyExec(self):
+        ws = self.getProperty("InputWorkspace").value
+        ws_append = self.getProperty("OutputWorkspace").value
+        ws_group = 'ws_group'
+        tolerance = self.getProperty("Tolerance").value
+        if not ws.sample().hasOrientedLattice():
+            FindUBUsingIndexedPeaks(PeaksWorkspace=ws,Tolerance=tolerance)
+        num,err=IndexPeaks(PeaksWorkspace=ws,Tolerance=tolerance)
+        logger.notice('Initial Number indexed: %s error: %s\n'%(num, err))
+        stats = StatisticsOfTableWorkspace(InputWorkspace=ws)
+        stat_col = stats.column('Statistic')
+        minR = int(stats.column('RunNumber')[stat_col.index('Minimum')])
+        maxR = int(stats.column('RunNumber')[stat_col.index('Maximum')]) + 1
+        AnalysisDataService.remove(stats.getName())
+        group = []
+        for run in range(minR, maxR):
+            FilterPeaks(InputWorkspace=ws, OutputWorkspace=str(run), FilterVariable='RunNumber',
+                        FilterValue=run, Operator='=')
+            run = mtd[str(run)]
+            peaks = run.getNumberPeaks()
+            if peaks == 0:
+                AnalysisDataService.remove(str(run))
+            else:
+                group.append(str(run))
+        GroupWorkspaces(InputWorkspaces=group, OutputWorkspace=ws_group)
+        OptimizeCrystalPlacement(PeaksWorkspace=ws_group, ModifiedPeaksWorkspace=ws_group, AdjustSampleOffsets=True,
+                                 MaxSamplePositionChangeMeters=0.005,MaxIndexingError=tolerance)
+        RenameWorkspace(InputWorkspace=str(minR),OutputWorkspace=ws_append)
+        for run in range(minR+1, maxR):
+            if AnalysisDataService.doesExist(str(run)):
+                CombinePeaksWorkspaces(LHSWorkspace=ws_append, RHSWorkspace=str(run),OutputWorkspace=ws_append)
+                logger.notice('Optimized %s sample position: %s\n'%(str(run),mtd[str(run)].getPeak(0).getSamplePos()))
+                AnalysisDataService.remove( str(run))
+        num,err=IndexPeaks(PeaksWorkspace=ws_append,Tolerance=tolerance)
+        logger.notice('After Optimization Number indexed: %s error: %s\n'%(num, err))
+        AnalysisDataService.remove(ws_group)
+        self.setProperty("OutputWorkspace", ws_append)
+
+
+# Register algorithm with Mantid
+AlgorithmFactory.subscribe(OptimizeCrystalPlacementByRun)
diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt b/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt
index 0baa419f1c91d63c35bd654abca34a52f652acc7..4603c39cde4eec159288be26df141e29c8fc97de 100644
--- a/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt
+++ b/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt
@@ -79,6 +79,7 @@ set ( TEST_PY_FILES
   MuonMaxEntTest.py
   NMoldyn4InterpolationTest.py
   NormaliseSpectraTest.py
+  OptimizeCrystalPlacementByRunTest.py
   ReflectometryReductionOneLiveDataTest.py
   ReflectometrySliceEventWorkspaceTest.py
   RetrieveRunInfoTest.py
diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/OptimizeCrystalPlacementByRunTest.py b/Framework/PythonInterface/test/python/plugins/algorithms/OptimizeCrystalPlacementByRunTest.py
new file mode 100644
index 0000000000000000000000000000000000000000..9429be6ff88a77351cf3ce73fff45f42079f8e5a
--- /dev/null
+++ b/Framework/PythonInterface/test/python/plugins/algorithms/OptimizeCrystalPlacementByRunTest.py
@@ -0,0 +1,32 @@
+# 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 +
+from __future__ import (absolute_import, division, print_function)
+
+import unittest
+from mantid.simpleapi import LoadIsawPeaks, FindUBUsingFFT, IndexPeaks, OptimizeCrystalPlacementByRun
+from mantid.api import mtd
+
+
+class OptimizeCrystalPlacementByRunTest(unittest.TestCase):
+    def test_simple(self):
+        ws=LoadIsawPeaks("calibrated.peaks")
+        FindUBUsingFFT(PeaksWorkspace=ws,MinD=2,MaxD=20,Tolerance=0.12)
+        IndexPeaks(PeaksWorkspace='ws',Tolerance=0.12)
+        wsd = OptimizeCrystalPlacementByRun(InputWorkspace=ws,OutputWorkspace='wsd',Tolerance=0.12)
+        result = mtd['wsd'].getPeak(0).getSamplePos()
+        self.assertAlmostEqual(result.getX(), -0.000678629)
+        self.assertAlmostEqual(result.getY(), -2.16033e-05)
+        self.assertAlmostEqual(result.getZ(), 0.00493278)
+        result = mtd['wsd'].getPeak(8).getSamplePos()
+        self.assertAlmostEqual(result.getX(), -0.0027929)
+        self.assertAlmostEqual(result.getY(), -0.00105681)
+        self.assertAlmostEqual(result.getZ(), 0.00497094)
+
+
+
+if __name__=="__main__":
+    unittest.main()
diff --git a/docs/source/algorithms/OptimizeCrystalPlacementByRun-v1.rst b/docs/source/algorithms/OptimizeCrystalPlacementByRun-v1.rst
new file mode 100644
index 0000000000000000000000000000000000000000..758e4c699cbc0ffd59b44ee0c1d41b3f669e54a1
--- /dev/null
+++ b/docs/source/algorithms/OptimizeCrystalPlacementByRun-v1.rst
@@ -0,0 +1,45 @@
+.. algorithm::
+
+.. summary::
+
+.. relatedalgorithms::
+
+.. properties::
+
+Description
+-----------
+
+This algorithm basically optimizes h,k, and l offsets from an integer by
+varying the parameters sample positions for each run in the peaks workspace.
+
+The crystal orientation matrix, :ref:`UB matrix <Lattice>`, from the
+PeaksWorkspace should index all the runs "very well". Otherwise iterations
+that slowly build a :ref:`UB matrix <Lattice>` with corrected sample
+orientations may be needed.
+
+
+Usage
+-----
+
+**Example:**
+
+.. testcode:: ExOptimizeCrystalPlacementByRun
+
+   ws=LoadIsawPeaks("calibrated.peaks")
+   FindUBUsingFFT(PeaksWorkspace=ws,MinD=2,MaxD=20,Tolerance=0.12)
+   IndexPeaks(PeaksWorkspace='ws',Tolerance=0.12)
+   wsd = OptimizeCrystalPlacementByRun(InputWorkspace=ws,OutputWorkspace='wsd',Tolerance=0.12)
+   print('Optimized %s sample position: %s'%(mtd['wsd'].getPeak(0).getRunNumber(),mtd['wsd'].getPeak(0).getSamplePos()))
+   print('Optimized %s sample position: %s'%(mtd['wsd'].getPeak(8).getRunNumber(),mtd['wsd'].getPeak(8).getSamplePos()))
+
+Output:
+
+.. testoutput:: ExOptimizeCrystalPlacementByRun
+
+   Optimized 71907 sample position: [-0.000678629,-2.16033e-05,0.00493278]
+   Optimized 72007 sample position: [-0.0027929,-0.00105681,0.00497094]
+
+
+.. categories::
+
+.. sourcelink::
diff --git a/docs/source/release/v3.14.0/diffraction.rst b/docs/source/release/v3.14.0/diffraction.rst
index 30df5d2faaeccedec9d20d8361829f72f8978756..131bbab7513af585788e1eb4ffb4d5e87798788c 100644
--- a/docs/source/release/v3.14.0/diffraction.rst
+++ b/docs/source/release/v3.14.0/diffraction.rst
@@ -49,6 +49,7 @@ Improvements
 - :ref:`IntegratePeaksUsingClusters <algm-IntegratePeaksUsingClusters>` will now treat NaN's as background.
 - SCD Event Data Reduction Diffraction Interface now adds goniometer for CORELLI and used proton charge as monitor count if no monitors are in input file.
 - :ref:`SetCrystalLocation <algm-SetCrystalLocation>` is a new algorithm to set the sample location in events workspaces.
+- :ref:`OptimizeCrystalPlacementByRun <algm-OptimizeCrystalPlacementByRun>` is new algorithm to update the sample position for each run in a peaks workspace.
 
 Bugfixes
 ########