From dccf0867f9f5e3d507d10824c67d09e952425427 Mon Sep 17 00:00:00 2001
From: DannyHindson <56295817+DannyHindson@users.noreply.github.com>
Date: Tue, 30 Mar 2021 16:48:29 +0100
Subject: [PATCH] Enhance conversions between dSpacing and TOF in ConvertUnits
 (#30163)

* Changes to dSpacing to TOF in ConvertUnits

Changes to the Unit class and ConvertUnits algorithm to allow
diffractometer constants (DIFA, DIFC, TZERO) stored in the instrument map
to be used in conversions between d spacing and TOF and v.v.

Rather than add 3 more parameters to lots of methods provided by the Unit
class I have changed some of the methods to take a std:map containing the
optional parameters relevant to only some units. I have included efixed
and delta in this new map

New diffractometerConstants method added to SpectrumInfo and DetectorInfo
to allow retrieval for a particular spectrum including averaging across
detectors. Also manages situations where some of the three constants are
missing.

New algorithm created called ApplyDiffCal to read the diffractometer
constants from a diffraction calibration table workspace and write them
into the instrument parameter map

Add diffractometer constants to the Show Detectors screen for elastic
workspaces

Remove the Diffraction.cpp unit and replace with calls to dSpacing unit class

Add ability to write difa, difc, tzero into a reduced instrument
geometry as part of EditInstrumentGeometry

* Fix unit tests using old unit conversion logic

* Add overload to spectrumInfo::diffractometerConstants

Add overload that doesn't require the 2nd warning dets parameter

* Remove unused variable declarations

* Replace costly call to spectrumInfo() with spectrumInfo object

* Correct incorrect capitalisation in unit name

* Fix bug in CreateDetectorTable

The exception handler in populateTable assumed the column counter
was always at 1. Reset the column counter to 0 by calling colValues.row
and then fill in default values.
Also add if statement to manage diff constants for monitors which
was the specific exception that caused me to find problem with general
exception handler

* Update HRPD system test ref files to accommodate new unit conversion

Update HRPD system test reference files to accommodate changes to the
unit conversions between TOF and dSpacing. As part of this I have updated
the reference file HRPD66031_splined. This is used as an input to the two
system tests that run a focus and it should match the output of the create
vanadium tests but it had got out of sync over the last couple of years
eg changes to Bragg peak stripping didn't result in an update to
HRPD66031_splined. So some changes in HRPD66031_splined that aren't purely
from the unit conversion logic change

* Make EnggFitPeaks use ConvertUnits for focussed ws

* Update engineering diffraction system test ref files

* Update calls to convertSingleFromTOF in spectrum viewer code

* Remove unused variable in spectrum viewer code

* Backout change to Qt4 unit because it causes cppcheck problem

* Various changes

The ParameterMap::diff method has been updated to take an extra
parameter representing a tolerance to be used when comparing double
values between two parameter maps
The overloaded == operator has been modified to call diff with a
zero tolerance to avoid duplicating code
CompareWorkspaces also updated to pass the value of CheckAllData
into diff so diff can stop after first change rather than going
through the whole map
The diff logic has also been speeded up to cope with larger parameter
maps now that diff constants stored (esp for GEM)
Various updates to doc test and system test files
Shorten UnitConversionParameters enum class name
Remove use of EMPTY_DBL() from ConvertUnits::getDetectorValues in
preparation for moving into ExperimentInfo class and also fix
RemoveSpectraTest unit test that relied on this

* Refactor code to retrieve unit related quantities from workspace

* Improve performance of getDetectorValues

* Delete Kernel Diffraction code files

* Delete Kernel Diffraction code files

* Performance improvements to diff constant lookup

* Delete Kernel Diffraction code files

* Performance improvements to diff constant lookup

Restrict diff constant lookup to elastic conversions to or
from d spacing
Just look up diff constants on the specified detector rather
than recursive look through all parents (slow if workspace
doesn't have any diff constants in the pmap at all)
Make the main loop in ConvertUnits parallel (as was the case
in AlignDetectors)

* Replace std::map with std::unordered_map for better performance

* Remove some duplicated code to fetch detector values

* Readd blank line to avoid showing up in diff

* Readd blank line to avoid showing up in diff

* Fix clang format

* Reinstate Efixed from property logic

* Fix failing unit tests

Also defer more decisions on whether missing detector values are
a problem to the code inside the Unit class rather than
experimentInfo::getDetectorValues

* Remove default (empty) value for extra params argument

This will force users to supply the diff constants for dSpacing
conversions rather than assuming supplying L2 and twoTheta is sufficient

* Move all detectors parameters into the map

* Fix Doxygen warnings

* Fix compilation error and expose new unit conv signature to python

* Various changes

Update new case using Unit::Initialize from merge
Reinstate dropped getEFixedForIndirect method
Remove one of the duplicates for the WISH Powder system tests
Also replace duplicate code to retrieve detector info with a call to
getDetectorValues instead
Move getDetectorValues into API::SpectrumInfo

* Fix compilation error in common widgets

* Fix bug in SaveGDA relating to d to TOF spacing conversion

difa and difc were back to front. Rather than swap here I've
changed the code to call the official unit conversion logic
in the dSpacing unit class

* Make momentum transfer (q) unit conversion use diff constants

Had to make minor adjustment to dSpacing::singleFromTOF to avoid
rounding problem on conversion max\min
Also changed qsquared over since this also used v similar formula
Removed epsilon from the check ranges call in unit tests for qsquared
because doesn't seem to be needed any more

* Couples of changes

Modify method of calculating average difc for a spectrum if there's no
calibration present (to avoid changing behaviour for most instruments)
Remove changes to EditInstrumentGeometry to attach diff constants
because can do this using ApplyDiffCal after EditInstrumentGeometry has
been run

* Fix clang format issue

* Fix compilation error on Windows

* Fix failing tests and compilation error

* Fix compilation error from missing include

* Extra way of initialising dSpacing and MomentumTransfer

Enable initialising dSpacing and MomentumTransfer using L2 and two theta
as an alternative to supplying the diff constants. This might be more
user friendly and it also moves the difc formula into the Unit class

Add utility functions to manage extra params map

* Add feature to ApplyDiffCal to clear calibration

* Various changes

Update some more unit tests to cope with changes MomentumTransfer unit
to make it use the diff constants
Improve validation on the L2\ttheta params in dSpacing unit
Allow negative DIFCs in conversions to TOF -
do this by setting up validation on to FromTOF direction only
Revert some files now that I'm doing more "input" averaging in DIFC
calculation on spectra with more than one detector

* Fix failing docs tests

* Rework MomentumTransfer to depend on difc only

* Fix linux compilation error

* Reinstate validation on MomentumTransfer unit class

* Revert some more tests files now that average difc calc reverted

* Revert change to remove local difc calculation so can do in separate PR

* Revert some more enginx system test files

* Revert some WISH system test files

* Revert SNS Powder Reduction ref file

* Revert another SNS ref file

* Update reference file

The changes are due to the StripVanadiumPeaks step in the SNSPowderReduction
algorithm. This algorithm seems v sensitive to even tiny changes in the input
workspace. The input workspace has some v small changes in at the ~15 decimal
place level as a result of this PR which updates the TOF\dSpacing unit conversion.
This causes the fits on some of the V peaks to behave differently and result
in the output having some much bigger differences on a 5-10% level
There is a composite function fit used in StripVanadiumPeaks (in FitPeak to be
precise) that seems a bit unstable and v sensitive to the input data

* Revert conversion from dSpacing to TOF to happen without calibration for ISIS Powder

* Update reference file

The changes are due to the StripVanadiumPeaks step. This algorithm seems very
sensitive to even tiny changes in the input workspace. The workspace that is
input to this step has some v small changes in at the ~15 decimal place level
as a result of this PR which updates the TOF\dSpacing unit conversion. This
causes the fits on some of the V peaks to behave differently and result in the
output having some much bigger differences on a 5-10% level. There is a
composite function fit used in StripVanadiumPeaks (in FitPeak to be precise)
that seems a bit unstable and v sensitive to the input data

* Temporary fix to get Polaris FocusTest to pass

The Polaris FocusTest currently has a couple of problems:
1) the sample empty run it uses looks wrong. 98532 is a V sample run
2) the POLARIS00098532_splined workspace seems to have been generatedthe
using some wrong\old crop ranges. This results in the normalization process
dividing by zero which generates infinities in the output of the test. There
are also some v large values (not quite double max) that get output which makes
setting an absolute tolerance difficult

* Update various ISIS Powder system test files to include masked spectra

* Two changes

Turn off caching in the SNSPowderRedux.PG3Analysis system test so the result of
the system test doesn't depend on the previous job that ran on Jenkins
Load the calibration in from a GSAS file in addition to the other instrument
geometry information so any subsequent unit conversions in Mantid work properly

* Widen tolerance so that Polaris focus system test passes

* Turn off caching in SNSPowderRedux system test

* Make it possible to override calibration to use on focused datasets
---
 Framework/API/inc/MantidAPI/ExperimentInfo.h  |   7 +
 Framework/API/inc/MantidAPI/SpectrumInfo.h    |  15 +
 Framework/API/src/ExperimentInfo.cpp          |  75 ++-
 Framework/API/src/IFunction.cpp               |  40 +-
 Framework/API/src/SpectrumInfo.cpp            | 176 +++++-
 .../inc/MantidAlgorithms/AlignDetectors.h     |   8 +-
 .../inc/MantidAlgorithms/ConvertUnits.h       |   7 -
 .../MantidAlgorithms/CreateDetectorTable.h    |   5 +-
 .../inc/MantidAlgorithms/PDCalibration.h      |   2 +-
 .../inc/MantidAlgorithms/RemoveBackground.h   |  11 +-
 .../inc/MantidAlgorithms/RemoveBins.h         |   2 -
 Framework/Algorithms/src/AddPeak.cpp          |  11 +-
 Framework/Algorithms/src/AlignDetectors.cpp   | 104 ++--
 .../src/CalculatePlaczekSelfScattering.cpp    |  21 +-
 .../Algorithms/src/CompareWorkspaces.cpp      |   6 +-
 .../Algorithms/src/ConvertSpectrumAxis.cpp    |  51 +-
 Framework/Algorithms/src/ConvertUnits.cpp     | 160 ++----
 .../src/ConvertUnitsUsingDetectorTable.cpp    |  22 +-
 .../Algorithms/src/CreateDetectorTable.cpp    |  43 +-
 Framework/Algorithms/src/GetAllEi.cpp         |  11 +-
 Framework/Algorithms/src/PDCalibration.cpp    |  64 ++-
 Framework/Algorithms/src/RemoveBackground.cpp |  59 +-
 Framework/Algorithms/src/RemoveBins.cpp       |  53 +-
 Framework/Algorithms/src/RemoveLowResTOF.cpp  |  12 +-
 .../Algorithms/test/ConvertSpectrumAxisTest.h |   2 +-
 Framework/Algorithms/test/ConvertUnitsTest.h  |  14 +-
 .../Algorithms/test/CreateDetectorTableTest.h |   4 +-
 Framework/Algorithms/test/PDCalibrationTest.h |  80 ++-
 .../Algorithms/test/RemoveBackgroundTest.h    |  11 +-
 Framework/Algorithms/test/RemoveSpectraTest.h |   8 +-
 Framework/Crystal/src/AnvredCorrection.cpp    |  36 +-
 Framework/Crystal/src/CentroidPeaks.cpp       |   6 +-
 Framework/Crystal/src/FindSXPeaksHelper.cpp   |  19 +-
 Framework/Crystal/src/LoadIsawPeaks.cpp       |   5 +-
 Framework/Crystal/src/LoadIsawSpectrum.cpp    |   6 +-
 Framework/Crystal/src/NormaliseVanadium.cpp   |  12 +-
 Framework/Crystal/src/PeakHKLErrors.cpp       |   6 +-
 Framework/Crystal/src/SCDCalibratePanels.cpp  |   5 +-
 Framework/Crystal/src/SCDCalibratePanels2.cpp |   6 +-
 .../src/SCDCalibratePanels2ObjFunc.cpp        |   6 +-
 Framework/Crystal/src/SCDPanelErrors.cpp      |   5 +-
 Framework/Crystal/src/SaveHKL.cpp             |   3 +-
 .../Crystal/test/FindSXPeaksHelperTest.h      |  20 +-
 .../test/IntegratePeakTimeSlicesTest.h        |   8 +-
 .../Crystal/test/SCDCalibratePanels2Test.h    |   7 +-
 .../CurveFitting/src/Algorithms/PawleyFit.cpp |   3 +-
 .../src/Functions/PawleyFunction.cpp          |   3 +-
 Framework/DataHandling/CMakeLists.txt         |   3 +
 .../inc/MantidDataHandling/ApplyDiffCal.h     |  44 ++
 .../inc/MantidDataHandling/LoadGSS.h          |   3 +-
 Framework/DataHandling/src/ApplyDiffCal.cpp   | 227 ++++++++
 Framework/DataHandling/src/LoadDiffCal.cpp    |   7 +-
 Framework/DataHandling/src/LoadGSS.cpp        |  12 +-
 Framework/DataHandling/src/SaveGDA.cpp        |  19 +-
 Framework/DataHandling/src/SaveGSS.cpp        |   7 +-
 .../DataHandling/test/ApplyDiffCalTest.h      | 131 +++++
 Framework/DataHandling/test/LoadGSSTest.h     |   2 +
 Framework/DataObjects/test/EventListTest.h    |   4 +-
 .../MantidGeometry/Instrument/DetectorInfo.h  |   6 +
 .../MantidGeometry/Instrument/ParameterMap.h  |   8 +-
 Framework/Geometry/src/Instrument.cpp         |  47 +-
 .../Geometry/src/Instrument/DetectorInfo.cpp  |  45 ++
 .../Geometry/src/Instrument/ParameterMap.cpp  | 108 ++--
 Framework/Kernel/CMakeLists.txt               |   3 -
 .../Kernel/inc/MantidKernel/Diffraction.h     |  36 --
 Framework/Kernel/inc/MantidKernel/Unit.h      | 157 ++++--
 .../Kernel/inc/MantidKernel/UnitConversion.h  |  12 +-
 Framework/Kernel/src/Diffraction.cpp          | 173 ------
 Framework/Kernel/src/Unit.cpp                 | 504 +++++++++++++-----
 Framework/Kernel/src/UnitConversion.cpp       |  55 +-
 Framework/Kernel/test/UnitTest.h              | 403 ++++++++++----
 .../UnitsConversionHelper.h                   |   5 +-
 .../src/PreprocessDetectorsToMD.cpp           |  13 +
 .../src/UnitsConversionHelper.cpp             |  73 ++-
 .../MDAlgorithms/test/MDTransfModQTest.h      |  11 +-
 Framework/MDAlgorithms/test/MDTransfQ3DTest.h |  23 +-
 .../test/PreprocessDetectorsToMDTest.h        |   2 +-
 .../test/UnitsConversionHelperTest.h          |  22 +-
 .../mantid/kernel/src/Exports/Unit.cpp        |  10 +
 .../kernel/src/Exports/UnitConversion.cpp     |  27 +-
 .../mantid/kernel/UnitConversionTest.py       |  19 +-
 Framework/SINQ/src/PoldiPeakSearch.cpp        |   4 +-
 .../src/WorkspaceCreationHelper.cpp           | 128 -----
 ...ned_66031_hrpd_new_072_01_corr.cal.nxs.md5 |   2 +-
 .../tests/framework/EnginXScriptTest.py       |  19 +-
 .../tests/framework/ISIS_PowderPolarisTest.py |   7 +-
 .../reference/HRPD66063_focused.nxs.md5       |   2 +-
 .../HRPD66063_focused_with_sac.nxs.md5        |   2 +-
 .../ISIS_Powder-GEM83605_FocusSempty.nxs.md5  |   2 +-
 ...owder-GEM83605_FocusSempty_abscorr.nxs.md5 |   2 +-
 .../ISIS_Powder-GEM87618_grouped.nxs.md5      |   2 +-
 .../ISIS_Powder-GEM87618_groupedGSAS1.nxs.md5 |   2 +-
 .../ISIS_Powder-PEARL00098472_splined.nxs.md5 |   2 +-
 ...SIS_Powder-PEARL00098507_tt70Atten.nxs.md5 |   2 +-
 ...owder-PEARL00098507_tt70NoEmptySub.nxs.md5 |   2 +-
 ...S_Powder-PEARL00098507_tt70_absorb.nxs.md5 |   2 +-
 .../ISIS_Powder-PEARL98494_grouped.nxs.md5    |   2 +-
 ...IS_Powder-POLARIS00098532_splined.nxs.md5} |   0
 ..._Powder-POLARIS00098532_unsplined.nxs.md5} |   0
 ...S_Powder-POLARIS98533_Auto_chopper.nxs.md5 |   2 +-
 ...IS_Powder-POLARIS98533_FocusSempty.nxs.md5 |   2 +-
 .../ISIS_Powder_PRL98472_tt70_all.nxs.md5     |   2 +-
 .../ISIS_Powder_PRL98472_tt70_groups.nxs.md5  |   2 +-
 .../ISIS_Powder_PRL98472_tt70_mods.nxs.md5    |   2 +-
 .../ISIS_Powder_PRL98472_tt70_trans.nxs.md5   |   2 +-
 .../reference/PG3_4844_reference.gsa.md5      |   2 +-
 .../reference/PG3_4866_reference.gsa.md5      |   2 +-
 docs/source/algorithms/ApplyDiffCal-v1.rst    |  36 ++
 .../algorithms/PreprocessDetectorsToMD-v1.rst |   2 +-
 docs/source/concepts/UnitFactory.rst          |  11 +-
 docs/source/release/v6.1.0/diffraction.rst    |   2 +
 .../Common/ImageInfoModelMatrixWS.h           |   2 -
 .../common/src/ImageInfoModelMatrixWS.cpp     |  72 +--
 .../Diffraction/isis_powder/abstract_inst.py  |   6 +
 .../isis_powder/routines/calibrate.py         |  10 +-
 .../Diffraction/isis_powder/routines/focus.py |   7 +-
 116 files changed, 2402 insertions(+), 1405 deletions(-)
 create mode 100644 Framework/DataHandling/inc/MantidDataHandling/ApplyDiffCal.h
 create mode 100644 Framework/DataHandling/src/ApplyDiffCal.cpp
 create mode 100644 Framework/DataHandling/test/ApplyDiffCalTest.h
 delete mode 100644 Framework/Kernel/inc/MantidKernel/Diffraction.h
 delete mode 100644 Framework/Kernel/src/Diffraction.cpp
 rename Testing/SystemTests/tests/framework/reference/{ISIS_Powder-POLARIS00098533_splined.nxs.md5 => ISIS_Powder-POLARIS00098532_splined.nxs.md5} (100%)
 rename Testing/SystemTests/tests/framework/reference/{ISIS_Powder-POLARIS00098533_unsplined.nxs.md5 => ISIS_Powder-POLARIS00098532_unsplined.nxs.md5} (100%)
 create mode 100644 docs/source/algorithms/ApplyDiffCal-v1.rst

diff --git a/Framework/API/inc/MantidAPI/ExperimentInfo.h b/Framework/API/inc/MantidAPI/ExperimentInfo.h
index 663416ad2db..7325cf488d9 100644
--- a/Framework/API/inc/MantidAPI/ExperimentInfo.h
+++ b/Framework/API/inc/MantidAPI/ExperimentInfo.h
@@ -12,6 +12,7 @@
 #include "MantidGeometry/Instrument_fwd.h"
 
 #include "MantidKernel/DeltaEMode.h"
+#include "MantidKernel/Unit.h"
 #include "MantidKernel/V3D.h"
 #include "MantidKernel/cow_ptr.h"
 
@@ -110,6 +111,12 @@ public:
   double getEFixed(const std::shared_ptr<const Geometry::IDetector> &detector =
                        std::shared_ptr<const Geometry::IDetector>{
                            nullptr}) const;
+  double getEFixedGivenEMode(
+      const std::shared_ptr<const Geometry::IDetector> &detector,
+      const Kernel::DeltaEMode::Type emode) const;
+  double getEFixedForIndirect(
+      const std::shared_ptr<const Geometry::IDetector> &detector,
+      const std::vector<std::string> &parameterNames) const;
   /// Set the efixed value for a given detector ID
   void setEFixed(const detid_t detID, const double value);
 
diff --git a/Framework/API/inc/MantidAPI/SpectrumInfo.h b/Framework/API/inc/MantidAPI/SpectrumInfo.h
index 1ba9f3a6b4e..0bb3ea26da4 100644
--- a/Framework/API/inc/MantidAPI/SpectrumInfo.h
+++ b/Framework/API/inc/MantidAPI/SpectrumInfo.h
@@ -8,6 +8,8 @@
 
 #include "MantidAPI/DllConfig.h"
 #include "MantidAPI/SpectrumInfoIterator.h"
+#include "MantidKernel/DeltaEMode.h"
+#include "MantidKernel/Unit.h"
 #include "MantidKernel/V3D.h"
 #include "MantidKernel/cow_ptr.h"
 
@@ -70,6 +72,11 @@ public:
   double azimuthal(const size_t index) const;
   std::pair<double, double> geographicalAngles(const size_t index) const;
   Kernel::V3D position(const size_t index) const;
+  Kernel::UnitParametersMap
+  diffractometerConstants(const size_t index,
+                          std::vector<detid_t> &uncalibratedDets) const;
+  Kernel::UnitParametersMap diffractometerConstants(const size_t index) const;
+  double difcUncalibrated(const size_t index) const;
   bool hasDetectors(const size_t index) const;
   bool hasUniqueDetector(const size_t index) const;
 
@@ -86,6 +93,14 @@ public:
   Kernel::V3D samplePosition() const;
   double l1() const;
 
+  void getDetectorValues(const Kernel::Unit &inputUnit,
+                         const Kernel::Unit &outputUnit,
+                         const Kernel::DeltaEMode::Type emode,
+                         const bool signedTheta, int64_t wsIndex,
+                         Kernel::UnitParametersMap &pmap) const;
+  void createDetectorIdLogMessages(const std::vector<detid_t> &detids,
+                                   int64_t wsIndex) const;
+
   SpectrumInfoIterator<SpectrumInfo> begin();
   SpectrumInfoIterator<SpectrumInfo> end();
   const SpectrumInfoIterator<const SpectrumInfo> cbegin() const;
diff --git a/Framework/API/src/ExperimentInfo.cpp b/Framework/API/src/ExperimentInfo.cpp
index f37808dbdc7..2192b715439 100644
--- a/Framework/API/src/ExperimentInfo.cpp
+++ b/Framework/API/src/ExperimentInfo.cpp
@@ -676,39 +676,70 @@ double ExperimentInfo::getEFixed(
     const std::shared_ptr<const Geometry::IDetector> &detector) const {
   populateIfNotLoaded();
   Kernel::DeltaEMode::Type emode = getEMode();
-  if (emode == Kernel::DeltaEMode::Direct) {
-    try {
-      return this->run().getPropertyValueAsType<double>("Ei");
-    } catch (Kernel::Exception::NotFoundError &) {
-      throw std::runtime_error(
-          "Experiment logs do not contain an Ei value. Have you run GetEi?");
-    }
-  } else if (emode == Kernel::DeltaEMode::Indirect) {
-    if (!detector)
-      throw std::runtime_error("ExperimentInfo::getEFixed - Indirect mode "
-                               "efixed requested without a valid detector.");
+  return getEFixedGivenEMode(detector, emode);
+}
+
+double ExperimentInfo::getEFixedForIndirect(
+    const std::shared_ptr<const Geometry::IDetector> &detector,
+    const std::vector<std::string> &parameterNames) const {
+  double efixed = 0.;
+  for (auto &parameterName : parameterNames) {
     Parameter_sptr par =
-        constInstrumentParameters().getRecursive(detector.get(), "Efixed");
+        constInstrumentParameters().getRecursive(detector.get(), parameterName);
     if (par) {
-      return par->value<double>();
+      efixed = par->value<double>();
     } else {
-      std::vector<double> efixedVec = detector->getNumberParameter("Efixed");
+      std::vector<double> efixedVec =
+          detector->getNumberParameter(parameterName);
       if (efixedVec.empty()) {
         int detid = detector->getID();
         IDetector_const_sptr detectorSingle =
             getInstrument()->getDetector(detid);
-        efixedVec = detectorSingle->getNumberParameter("Efixed");
+        efixedVec = detectorSingle->getNumberParameter(parameterName);
       }
       if (!efixedVec.empty()) {
-        return efixedVec.at(0);
-      } else {
-        std::ostringstream os;
-        os << "ExperimentInfo::getEFixed - Indirect mode efixed requested but "
-              "detector has no Efixed parameter attached. ID="
-           << detector->getID();
-        throw std::runtime_error(os.str());
+        efixed = efixedVec.at(0);
+      }
+    }
+  }
+  if (efixed == 0.) {
+    std::ostringstream os;
+    os << "ExperimentInfo::getEFixed - Indirect mode efixed requested but "
+          "detector has no Efixed parameter attached. ID="
+       << detector->getID();
+    throw std::runtime_error(os.str());
+  }
+  return efixed;
+}
+
+/**
+ * Easy access to the efixed value for this run & detector
+ * @param detector :: The detector object to ask for the efixed mode. Only
+ * required for Indirect mode
+ * @param emode :: enum value indicating whether elastic, direct or indirect
+ * @return The current efixed value
+ */
+double ExperimentInfo::getEFixedGivenEMode(
+    const std::shared_ptr<const Geometry::IDetector> &detector,
+    const Kernel::DeltaEMode::Type emode) const {
+  if (emode == Kernel::DeltaEMode::Direct) {
+    double efixed = 0.;
+    for (auto &parameterName : {"Ei", "EnergyRequested", "EnergyEstimate"}) {
+      if (run().hasProperty(parameterName)) {
+        efixed = run().getPropertyValueAsType<double>(parameterName);
+        break;
       }
     }
+    if (efixed == 0.) {
+      throw std::runtime_error("Experiment logs do not contain an Ei "
+                               "value. Have you run GetEi?");
+    }
+    return efixed;
+  } else if (emode == Kernel::DeltaEMode::Indirect) {
+    if (!detector)
+      throw std::runtime_error("ExperimentInfo::getEFixed - Indirect mode "
+                               "efixed requested without a valid detector.");
+    return getEFixedForIndirect(detector, {"Efixed", "EFixed-val"});
   } else {
     throw std::runtime_error("ExperimentInfo::getEFixed - EFixed requested for "
                              "elastic mode, don't know what to do!");
diff --git a/Framework/API/src/IFunction.cpp b/Framework/API/src/IFunction.cpp
index a69fe78f87f..9cc5aec67ea 100644
--- a/Framework/API/src/IFunction.cpp
+++ b/Framework/API/src/IFunction.cpp
@@ -1143,12 +1143,12 @@ void IFunction::setMatrixWorkspace(
               if (centreUnit) {
                 g_log.debug()
                     << "For FitParameter " << parameterName(i)
-                    << " centre of peak before any unit convertion is "
+                    << " centre of peak before any unit conversion is "
                     << centreValue << '\n';
                 centreValue =
                     convertValue(centreValue, centreUnit, workspace, wi);
                 g_log.debug() << "For FitParameter " << parameterName(i)
-                              << " centre of peak after any unit convertion is "
+                              << " centre of peak after any unit conversion is "
                               << centreValue << '\n';
               }
 
@@ -1165,11 +1165,11 @@ void IFunction::setMatrixWorkspace(
                     fitParam.getLookUpTable().getYUnit(); // from table
                 g_log.debug()
                     << "The FitParameter " << parameterName(i) << " = "
-                    << paramValue << " before y-unit convertion\n";
+                    << paramValue << " before y-unit conversion\n";
                 paramValue /= convertValue(1.0, resultUnit, workspace, wi);
                 g_log.debug()
                     << "The FitParameter " << parameterName(i) << " = "
-                    << paramValue << " after y-unit convertion\n";
+                    << paramValue << " after y-unit conversion\n";
               } else {
                 // so from formula
 
@@ -1195,12 +1195,12 @@ void IFunction::setMatrixWorkspace(
                     p.SetExpr(resultUnitStr);
                     g_log.debug() << "The FitParameter " << parameterName(i)
                                   << " = " << paramValue
-                                  << " before result-unit convertion (using "
+                                  << " before result-unit conversion (using "
                                   << resultUnitStr << ")\n";
                     paramValue *= p.Eval();
                     g_log.debug()
                         << "The FitParameter " << parameterName(i) << " = "
-                        << paramValue << " after result-unit convertion\n";
+                        << paramValue << " after result-unit conversion\n";
                   } catch (mu::Parser::exception_type &e) {
                     g_log.error()
                         << "Cannot convert formula unit to workspace unit"
@@ -1308,31 +1308,25 @@ void IFunction::convertValue(std::vector<double> &values,
     if (sample == nullptr) {
       g_log.error()
           << "No sample defined instrument. Cannot convert units for function\n"
-          << "Ignore convertion.";
+          << "Ignore conversion.";
       return;
     }
     const auto &spectrumInfo = ws->spectrumInfo();
     double l1 = spectrumInfo.l1();
     // If this is a monitor then l1+l2 = source-detector distance and twoTheta=0
-    double l2 = spectrumInfo.l2(wsIndex);
-    double twoTheta(0.0);
-    if (!spectrumInfo.isMonitor(wsIndex))
-      twoTheta = spectrumInfo.twoTheta(wsIndex);
-    auto emode = static_cast<int>(ws->getEMode());
-    double efixed(0.0);
+    auto emode = ws->getEMode();
+
+    Kernel::UnitParametersMap pmap{};
+    spectrumInfo.getDetectorValues(*wsUnit, *outUnit, emode, false, wsIndex,
+                                   pmap);
+    std::vector<double> emptyVec;
     try {
-      std::shared_ptr<const Geometry::IDetector> det(
-          &spectrumInfo.detector(wsIndex), NoDeleting());
-      efixed = ws->getEFixed(det);
+      wsUnit->toTOF(values, emptyVec, l1, emode, pmap);
+      outUnit->fromTOF(values, emptyVec, l1, emode, pmap);
     } catch (std::exception &) {
-      // assume elastic
-      efixed = 0.0;
-      emode = 0;
+      throw std::runtime_error("Unable to perform unit conversion to " +
+                               outUnit->unitID());
     }
-
-    std::vector<double> emptyVec;
-    wsUnit->toTOF(values, emptyVec, l1, l2, twoTheta, emode, efixed, 0.0);
-    outUnit->fromTOF(values, emptyVec, l1, l2, twoTheta, emode, efixed, 0.0);
   }
 }
 
diff --git a/Framework/API/src/SpectrumInfo.cpp b/Framework/API/src/SpectrumInfo.cpp
index 50b1ec904cc..3cc369cac7d 100644
--- a/Framework/API/src/SpectrumInfo.cpp
+++ b/Framework/API/src/SpectrumInfo.cpp
@@ -8,17 +8,23 @@
 #include "MantidAPI/ExperimentInfo.h"
 #include "MantidAPI/SpectrumInfoIterator.h"
 #include "MantidBeamline/SpectrumInfo.h"
+#include "MantidGeometry/Instrument.h"
 #include "MantidGeometry/Instrument/DetectorGroup.h"
 #include "MantidGeometry/Instrument/DetectorInfo.h"
 #include "MantidKernel/Exception.h"
+#include "MantidKernel/Logger.h"
 #include "MantidKernel/MultiThreaded.h"
 #include "MantidTypes/SpectrumDefinition.h"
 
 #include <algorithm>
 #include <memory>
 
+using namespace Mantid::Kernel;
+
 namespace Mantid {
 namespace API {
+/// static logger object
+Kernel::Logger g_log("ExperimentInfo");
 
 SpectrumInfo::SpectrumInfo(const Beamline::SpectrumInfo &spectrumInfo,
                            const ExperimentInfo &experimentInfo,
@@ -140,7 +146,175 @@ Kernel::V3D SpectrumInfo::position(const size_t index) const {
   return newPos / static_cast<double>(spectrumDefinition(index).size());
 }
 
-/// Returns true if the spectrum is associated with detectors in the instrument.
+/** Calculate average diffractometer constants (DIFA, DIFC, TZERO) of detectors
+ * associated with this spectrum. Use calibrated values where possible, filling
+ * in with uncalibrated values where they're missing
+ *  @param index Index of the spectrum that constants are required for
+ *  @param warningDets A vector containing the det ids where an uncalibrated
+ * value was used in the situation where some dets have calibrated values and
+ * some don't
+ *  @return map containing the average constants
+ */
+UnitParametersMap
+SpectrumInfo::diffractometerConstants(const size_t index,
+                                      std::vector<detid_t> &warningDets) const {
+  if (m_detectorInfo.isScanning()) {
+    throw std::runtime_error("Retrieval of diffractometer constants not "
+                             "implemented for scanning instrument");
+  }
+  auto spectrumDef = checkAndGetSpectrumDefinition(index);
+  std::vector<size_t> detectorIndicesOnly;
+  std::vector<detid_t> calibratedDets;
+  std::vector<detid_t> uncalibratedDets;
+  std::transform(spectrumDef.begin(), spectrumDef.end(),
+                 std::back_inserter(detectorIndicesOnly),
+                 [](auto const &pair) { return pair.first; });
+  double difa{0.}, difc{0.}, tzero{0.};
+  for (const auto &detIndex : detectorIndicesOnly) {
+    auto newDiffConstants = m_detectorInfo.diffractometerConstants(
+        detIndex, calibratedDets, uncalibratedDets);
+    difa += std::get<0>(newDiffConstants);
+    difc += std::get<1>(newDiffConstants);
+    tzero += std::get<2>(newDiffConstants);
+  }
+
+  if (calibratedDets.size() > 0 && uncalibratedDets.size() > 0) {
+    warningDets.insert(warningDets.end(), uncalibratedDets.begin(),
+                       uncalibratedDets.end());
+  };
+  // if no calibration is found then return difc only based on the average
+  // of the detector L2 and twoThetas.
+  if (calibratedDets.size() == 0) {
+    return {{UnitParams::difc, difcUncalibrated(index)}};
+  }
+  return {{UnitParams::difa,
+           difa / static_cast<double>(spectrumDefinition(index).size())},
+          {UnitParams::difc,
+           difc / static_cast<double>(spectrumDefinition(index).size())},
+          {UnitParams::tzero,
+           tzero / static_cast<double>(spectrumDefinition(index).size())}};
+}
+
+/** Calculate average diffractometer constants (DIFA, DIFC, TZERO) of
+ * detectors associated with this spectrum. Use calibrated values where
+ * possible, filling in with uncalibrated values where they're missing
+ *  @param index Index of the spectrum that constants are required for
+ *  @return map containing the average constants
+ */
+UnitParametersMap
+SpectrumInfo::diffractometerConstants(const size_t index) const {
+  std::vector<int> warningDets;
+  return diffractometerConstants(index, warningDets);
+}
+
+/** Calculate average uncalibrated DIFC value of detectors associated with
+ * this spectrum
+ *  @param index Index of the spectrum that DIFC is required for
+ *  @return The average DIFC
+ */
+double SpectrumInfo::difcUncalibrated(const size_t index) const {
+  // calculate difc based on the average of the detector L2 and twoThetas.
+  // This will be different to the average of the per detector difcs. This is
+  // for backwards compatibility because Mantid always used to calculate
+  // spectrum level difc's this way
+  return 1. / Kernel::Units::tofToDSpacingFactor(l1(), l2(index),
+                                                 twoTheta(index), 0.);
+}
+
+/** Get the detector values relevant to unit conversion for a workspace index
+ * @param inputUnit :: The input unit (Empty implies "all")
+ * @param outputUnit :: The output unit (Empty implies "all")
+ * @param emode :: The energy mode
+ * @param signedTheta :: Return twotheta with sign or without
+ * @param wsIndex :: The workspace index
+ * @param pmap :: a map containing values for conversion parameters that are
+required by unit classes to perform their conversions eg efixed. It can
+contain values on the way in if a look up isn't desired here eg if value
+supplied in parameters to the calling algorithm
+ */
+void SpectrumInfo::getDetectorValues(const Kernel::Unit &inputUnit,
+                                     const Kernel::Unit &outputUnit,
+                                     const Kernel::DeltaEMode::Type emode,
+                                     const bool signedTheta, int64_t wsIndex,
+                                     UnitParametersMap &pmap) const {
+  if (!hasDetectors(wsIndex))
+    return;
+  pmap[UnitParams::l2] = l2(wsIndex);
+
+  if (!isMonitor(wsIndex)) {
+    // The scattering angle for this detector (in radians).
+    try {
+      if (signedTheta)
+        pmap[UnitParams::twoTheta] = signedTwoTheta(wsIndex);
+      else
+        pmap[UnitParams::twoTheta] = twoTheta(wsIndex);
+    } catch (const std::runtime_error &e) {
+      g_log.warning(e.what());
+    }
+    if (emode != Kernel::DeltaEMode::Elastic &&
+        pmap.find(UnitParams::efixed) == pmap.end()) {
+      std::shared_ptr<const Geometry::IDetector> det(&detector(wsIndex),
+                                                     Mantid::NoDeleting());
+      try {
+        pmap[UnitParams::efixed] =
+            m_experimentInfo.getEFixedGivenEMode(det, emode);
+        g_log.debug() << "Detector: " << det->getID()
+                      << " EFixed: " << pmap[UnitParams::efixed] << "\n";
+      } catch (std::runtime_error) {
+        // let the unit classes work out if this is a problem
+      }
+    }
+
+    std::vector<detid_t> warnDetIds;
+    try {
+      std::set<std::string> diffConstUnits = {"dSpacing", "MomentumTransfer",
+                                              "Empty"};
+      if ((emode == Kernel::DeltaEMode::Elastic) &&
+          (diffConstUnits.count(inputUnit.unitID()) ||
+           diffConstUnits.count(outputUnit.unitID()))) {
+        auto diffConstsMap = diffractometerConstants(wsIndex, warnDetIds);
+        pmap.insert(diffConstsMap.begin(), diffConstsMap.end());
+        if (warnDetIds.size() > 0) {
+          createDetectorIdLogMessages(warnDetIds, wsIndex);
+        }
+      } else {
+        pmap[UnitParams::difc] = difcUncalibrated(wsIndex);
+      }
+    } catch (const std::runtime_error &e) {
+      g_log.warning(e.what());
+    }
+  } else {
+    pmap[UnitParams::twoTheta] = 0.0;
+    pmap[UnitParams::efixed] = DBL_MIN;
+    // Energy transfer is meaningless for a monitor, so set l2 to 0.
+    if (outputUnit.unitID().find("DeltaE") != std::string::npos) {
+      pmap[UnitParams::l2] = 0.0;
+    }
+    pmap[UnitParams::difc] = 0;
+  }
+}
+
+void SpectrumInfo::createDetectorIdLogMessages(
+    const std::vector<detid_t> &detids, int64_t wsIndex) const {
+  std::string detIDstring;
+  auto iter = detids.begin();
+  auto itEnd = detids.end();
+  for (; iter != itEnd; ++iter) {
+    detIDstring += std::to_string(*iter) + ",";
+  }
+
+  if (!detIDstring.empty()) {
+    detIDstring.pop_back(); // Drop last comma
+  }
+  g_log.warning(
+      "Incomplete set of calibrated diffractometer constants found for "
+      "workspace index" +
+      std::to_string(wsIndex) + ". Using uncalibrated values for detectors " +
+      detIDstring);
+}
+
+/// Returns true if the spectrum is associated with detectors in the
+/// instrument.
 bool SpectrumInfo::hasDetectors(const size_t index) const {
   // Workspaces can contain invalid detector IDs. Those IDs will be silently
   // ignored here until this is fixed.
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/AlignDetectors.h b/Framework/Algorithms/inc/MantidAlgorithms/AlignDetectors.h
index b2dba6d8716..c2fc047d533 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/AlignDetectors.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/AlignDetectors.h
@@ -7,6 +7,7 @@
 #pragma once
 
 #include "MantidAPI/Algorithm.h"
+#include "MantidAPI/DeprecatedAlgorithm.h"
 #include "MantidAPI/ITableWorkspace_fwd.h"
 #include "MantidAlgorithms/DllConfig.h"
 #include "MantidDataObjects/OffsetsWorkspace.h"
@@ -39,7 +40,8 @@ class ConversionFactors;
     @author Russell Taylor, Tessella Support Services plc
     @date 18/08/2008
 */
-class MANTID_ALGORITHMS_DLL AlignDetectors : public API::Algorithm {
+class MANTID_ALGORITHMS_DLL AlignDetectors : public API::Algorithm,
+                                             public API::DeprecatedAlgorithm {
 public:
   AlignDetectors();
 
@@ -69,9 +71,7 @@ private:
   void exec() override;
 
   void align(const ConversionFactors &converter, API::Progress &progress,
-             API::MatrixWorkspace &outputWS);
-  void align(const ConversionFactors &converter, API::Progress &progress,
-             DataObjects::EventWorkspace &outputWS);
+             API::MatrixWorkspace_sptr &outputWS);
 
   void loadCalFile(const API::MatrixWorkspace_sptr &inputWS,
                    const std::string &filename);
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/ConvertUnits.h b/Framework/Algorithms/inc/MantidAlgorithms/ConvertUnits.h
index aef3ddfa017..ddf1e23f7dd 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/ConvertUnits.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/ConvertUnits.h
@@ -103,13 +103,6 @@ protected:
   convertQuickly(const API::MatrixWorkspace_const_sptr &inputWS,
                  const double &factor, const double &power);
 
-  /// Internal function to gather detector specific L2, theta and efixed values
-  bool getDetectorValues(const API::SpectrumInfo &spectrumInfo,
-                         const Kernel::Unit &outputUnit, int emode,
-                         const API::MatrixWorkspace &ws, const bool signedTheta,
-                         int64_t wsIndex, double &efixed, double &l2,
-                         double &twoTheta);
-
   /// Convert the workspace units using TOF as an intermediate step in the
   /// conversion
   virtual API::MatrixWorkspace_sptr
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/CreateDetectorTable.h b/Framework/Algorithms/inc/MantidAlgorithms/CreateDetectorTable.h
index 3f13c9c229b..bfd4aaf2033 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/CreateDetectorTable.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/CreateDetectorTable.h
@@ -68,9 +68,10 @@ void populateTable(Mantid::API::ITableWorkspace_sptr &t,
                    const Mantid::Geometry::PointingAlong &beamAxisIndex,
                    const double sampleDist, const bool isScanning,
                    const bool include_data, const bool calcQ,
-                   Kernel::Logger &logger);
+                   const bool includeDiffConstants, Kernel::Logger &logger);
 std::vector<std::pair<std::string, std::string>>
-createColumns(const bool isScanning, const bool includeData, const bool calcQ);
+createColumns(const bool isScanning, const bool includeData, const bool calcQ,
+              const bool hasDiffConstants);
 
 } // namespace Algorithms
 } // namespace Mantid
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/PDCalibration.h b/Framework/Algorithms/inc/MantidAlgorithms/PDCalibration.h
index e4affc14c7e..16a284bb66c 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/PDCalibration.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/PDCalibration.h
@@ -44,7 +44,7 @@ private:
   void createCalTableFromExisting();
   void createCalTableNew();
   void createInformationWorkspaces();
-  std::function<double(double)>
+  std::tuple<double, double, double>
   getDSpacingToTof(const std::set<detid_t> &detIds);
   std::vector<double> dSpacingWindows(const std::vector<double> &centres,
                                       const double widthMax);
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/RemoveBackground.h b/Framework/Algorithms/inc/MantidAlgorithms/RemoveBackground.h
index 0b4cbfa584c..66183e705f5 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/RemoveBackground.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/RemoveBackground.h
@@ -9,6 +9,7 @@
 #include "MantidAPI/Algorithm.h"
 #include "MantidAlgorithms/DllConfig.h"
 #include "MantidGeometry/IComponent.h"
+#include "MantidKernel/DeltaEMode.h"
 #include "MantidKernel/cow_ptr.h"
 
 namespace Mantid {
@@ -46,7 +47,8 @@ public:
   BackgroundHelper(const BackgroundHelper &) = delete;
 
   void initialize(const API::MatrixWorkspace_const_sptr &bkgWS,
-                  const API::MatrixWorkspace_sptr &sourceWS, int emode,
+                  const API::MatrixWorkspace_sptr &sourceWS,
+                  Kernel::DeltaEMode::Type emode,
                   Kernel::Logger *pLog = nullptr, int nThreads = 1,
                   bool inPlace = true, bool nullifyNegative = false);
   void removeBackground(int nHist, HistogramData::HistogramX &x_data,
@@ -81,16 +83,11 @@ private:
   // workspace
   double m_ErrSq;
   // energy conversion mode
-  int m_Emode;
-  // incident for direct or analysis for indirect energy for units conversion
-  double m_Efix;
+  Kernel::DeltaEMode::Type m_Emode;
   // if true, negative signals are nullified
   bool m_nullifyNegative;
   // removing negative values from ws with background removed previously.
   bool m_previouslyRemovedBkgMode;
-
-  // get Ei attached to direct or indirect instrument workspace
-  double getEi(const API::MatrixWorkspace_const_sptr &inputWS) const;
 };
 
 class MANTID_ALGORITHMS_DLL RemoveBackground : public API::Algorithm {
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/RemoveBins.h b/Framework/Algorithms/inc/MantidAlgorithms/RemoveBins.h
index b3862afea15..4e4b15b3ef5 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/RemoveBins.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/RemoveBins.h
@@ -72,8 +72,6 @@ private:
 
   void crop(const double &start, const double &end);
   void transformRangeUnit(const int index, double &startX, double &endX);
-  void calculateDetectorPosition(const int index, double &l1, double &l2,
-                                 double &twoTheta);
   int findIndex(const double &value, const HistogramData::HistogramX &vec);
   void RemoveFromEnds(int start, int end, HistogramData::HistogramY &Y,
                       HistogramData::HistogramE &E);
diff --git a/Framework/Algorithms/src/AddPeak.cpp b/Framework/Algorithms/src/AddPeak.cpp
index 63be9394c47..32b45a3b594 100644
--- a/Framework/Algorithms/src/AddPeak.cpp
+++ b/Framework/Algorithms/src/AddPeak.cpp
@@ -83,6 +83,9 @@ void AddPeak::exec() {
   double Qz = 1.0 - cos(theta2);
   double l1 = detectorInfo.l1();
   double l2 = detectorInfo.l2(detectorIndex);
+  std::vector<int> emptyWarningVec;
+  auto [difa, difc, tzero] = detectorInfo.diffractometerConstants(
+      detectorIndex, emptyWarningVec, emptyWarningVec);
 
   Mantid::Kernel::Unit_sptr unit = runWS->getAxis(0)->unit();
   if (unit->unitID() != "TOF") {
@@ -112,7 +115,13 @@ void AddPeak::exec() {
     }
     std::vector<double> xdata(1, tof);
     std::vector<double> ydata;
-    unit->toTOF(xdata, ydata, l1, l2, theta2, emode, efixed, 0.0);
+    unit->toTOF(xdata, ydata, l1, emode,
+                {{Kernel::UnitParams::l2, l2},
+                 {Kernel::UnitParams::twoTheta, theta2},
+                 {Kernel::UnitParams::efixed, efixed},
+                 {Kernel::UnitParams::difa, difa},
+                 {Kernel::UnitParams::difc, difc},
+                 {Kernel::UnitParams::tzero, tzero}});
     tof = xdata[0];
   }
 
diff --git a/Framework/Algorithms/src/AlignDetectors.cpp b/Framework/Algorithms/src/AlignDetectors.cpp
index 7b2e08c9c10..b9981513252 100644
--- a/Framework/Algorithms/src/AlignDetectors.cpp
+++ b/Framework/Algorithms/src/AlignDetectors.cpp
@@ -17,7 +17,6 @@
 #include "MantidDataObjects/EventWorkspace.h"
 #include "MantidDataObjects/OffsetsWorkspace.h"
 #include "MantidKernel/CompositeValidator.h"
-#include "MantidKernel/Diffraction.h"
 #include "MantidKernel/PhysicalConstants.h"
 #include "MantidKernel/UnitFactory.h"
 #include "MantidKernel/V3D.h"
@@ -49,8 +48,8 @@ public:
     this->generateDetidToRow(table);
   }
 
-  std::function<double(double)>
-  getConversionFunc(const std::set<detid_t> &detIds) const {
+  std::tuple<double, double, double>
+  getDiffConstants(const std::set<detid_t> &detIds) const {
     const std::set<size_t> rows = this->getRow(detIds);
     double difc = 0.;
     double difa = 0.;
@@ -67,7 +66,7 @@ public:
       tzero = norm * tzero;
     }
 
-    return Kernel::Diffraction::getTofToDConversionFunc(difc, difa, tzero);
+    return {difc, difa, tzero};
   }
 
 private:
@@ -87,6 +86,16 @@ private:
         rows.insert(rowIter->second);
       }
     }
+    if (rows.empty()) {
+      std::string detIdsStr = std::accumulate(
+          std::begin(detIds), std::end(detIds), std::string{},
+          [](const std::string &a, const detid_t &b) {
+            return a.empty() ? std::to_string(b) : a + ',' + std::to_string(b);
+          });
+      throw Exception::NotFoundError(
+          "None of the detectors were found in the calibration table",
+          detIdsStr);
+    }
     return rows;
   }
 
@@ -110,8 +119,11 @@ const std::string AlignDetectors::summary() const {
          "values to account for small errors in the detector positions.";
 }
 
-/// (Empty) Constructor
-AlignDetectors::AlignDetectors() : m_numberOfSpectra(0) {}
+/// Constructor
+AlignDetectors::AlignDetectors() : m_numberOfSpectra(0) {
+  useAlgorithm("ConvertUnits");
+  deprecatedDate("2021-01-04");
+}
 
 void AlignDetectors::init() {
   auto wsValidator = std::make_shared<CompositeValidator>();
@@ -262,60 +274,58 @@ void AlignDetectors::exec() {
 
   Progress progress(this, 0.0, 1.0, m_numberOfSpectra);
 
-  auto eventW = std::dynamic_pointer_cast<EventWorkspace>(outputWS);
-  if (eventW) {
-    align(converter, progress, *eventW);
-  } else {
-    align(converter, progress, *outputWS);
-  }
+  align(converter, progress, outputWS);
 }
 
 void AlignDetectors::align(const ConversionFactors &converter,
-                           Progress &progress, MatrixWorkspace &outputWS) {
-  PARALLEL_FOR_IF(Kernel::threadSafe(outputWS))
+                           Progress &progress, MatrixWorkspace_sptr &outputWS) {
+  auto eventW = std::dynamic_pointer_cast<EventWorkspace>(outputWS);
+  PARALLEL_FOR_IF(Kernel::threadSafe(*outputWS))
   for (int64_t i = 0; i < m_numberOfSpectra; ++i) {
     PARALLEL_START_INTERUPT_REGION
     try {
       // Get the input spectrum number at this workspace index
-      auto &spec = outputWS.getSpectrum(size_t(i));
-      auto toDspacing = converter.getConversionFunc(spec.getDetectorIDs());
-
-      auto &x = outputWS.mutableX(i);
-      std::transform(x.begin(), x.end(), x.begin(), toDspacing);
-    } catch (const Exception::NotFoundError &) {
-      // Zero the data in this case
-      outputWS.setHistogram(i, BinEdges(outputWS.x(i).size()),
-                            Counts(outputWS.y(i).size()));
+      auto &spec = outputWS->getSpectrum(size_t(i));
+      auto [difc, difa, tzero] =
+          converter.getDiffConstants(spec.getDetectorIDs());
+
+      auto &x = outputWS->dataX(i);
+      Kernel::Units::dSpacing dSpacingUnit;
+      std::vector<double> yunused;
+      dSpacingUnit.fromTOF(
+          x, yunused, -1., 0,
+          UnitParametersMap{{Kernel::UnitParams::difa, difa},
+                            {Kernel::UnitParams::difc, difc},
+                            {Kernel::UnitParams::tzero, tzero}});
+
+      if (eventW) {
+        Kernel::Units::TOF tofUnit;
+        tofUnit.initialize(0, 0, {});
+        // EventWorkspace part, modifying the EventLists.
+        eventW->getSpectrum(i).convertUnitsViaTof(&tofUnit, &dSpacingUnit);
+      }
+    } catch (const std::runtime_error &) {
+      if (!eventW) {
+        // Zero the data in this case (detectors not found in cal table or
+        // conversion fails)
+        outputWS->setHistogram(i, BinEdges(outputWS->x(i).size()),
+                               Counts(outputWS->y(i).size()));
+      }
     }
     progress.report();
     PARALLEL_END_INTERUPT_REGION
   }
   PARALLEL_CHECK_INTERUPT_REGION
-}
-
-void AlignDetectors::align(const ConversionFactors &converter,
-                           Progress &progress, EventWorkspace &outputWS) {
-  PARALLEL_FOR_NO_WSP_CHECK()
-  for (int64_t i = 0; i < m_numberOfSpectra; ++i) {
-    PARALLEL_START_INTERUPT_REGION
-
-    auto toDspacing = converter.getConversionFunc(
-        outputWS.getSpectrum(size_t(i)).getDetectorIDs());
-    outputWS.getSpectrum(i).convertTof(toDspacing);
-
-    progress.report();
-    PARALLEL_END_INTERUPT_REGION
-  }
-  PARALLEL_CHECK_INTERUPT_REGION
-
-  if (outputWS.getTofMin() < 0.) {
-    std::stringstream msg;
-    msg << "Something wrong with the calibration. Negative minimum d-spacing "
-           "created. d_min = "
-        << outputWS.getTofMin() << " d_max " << outputWS.getTofMax();
-    g_log.warning(msg.str());
+  if (eventW) {
+    if (eventW->getTofMin() < 0.) {
+      std::stringstream msg;
+      msg << "Something wrong with the calibration. Negative minimum d-spacing "
+             "created. d_min = "
+          << eventW->getTofMin() << " d_max " << eventW->getTofMax();
+      g_log.warning(msg.str());
+    }
+    eventW->clearMRU();
   }
-  outputWS.clearMRU();
 }
 
 Parallel::ExecutionMode AlignDetectors::getParallelExecutionMode(
diff --git a/Framework/Algorithms/src/CalculatePlaczekSelfScattering.cpp b/Framework/Algorithms/src/CalculatePlaczekSelfScattering.cpp
index 3417c43b262..c4fdc58b372 100644
--- a/Framework/Algorithms/src/CalculatePlaczekSelfScattering.cpp
+++ b/Framework/Algorithms/src/CalculatePlaczekSelfScattering.cpp
@@ -163,12 +163,23 @@ void CalculatePlaczekSelfScattering::exec() {
     auto &y = outputWS->mutableY(specIndex);
     auto &x = outputWS->mutableX(specIndex);
     if (!specInfo.isMonitor(specIndex) && !(specInfo.l2(specIndex) == 0.0)) {
-      const double pathLength = specInfo.l1() + specInfo.l2(specIndex);
-      const double f = specInfo.l1() / pathLength;
-      const double sinThetaBy2 = sin(specInfo.twoTheta(specIndex) / 2.0);
       Kernel::Units::Wavelength wavelength;
-      wavelength.initialize(specInfo.l1(), specInfo.l2(specIndex),
-                            specInfo.twoTheta(specIndex), 0, 1.0, 1.0);
+      Kernel::Units::TOF tof;
+      Kernel::UnitParametersMap pmap{};
+      double l1 = specInfo.l1();
+      specInfo.getDetectorValues(wavelength, tof, Kernel::DeltaEMode::Elastic,
+                                 false, specIndex, pmap);
+      double l2 = 0., twoTheta = 0.;
+      if (pmap.find(Kernel::UnitParams::l2) != pmap.end()) {
+        l2 = pmap[Kernel::UnitParams::l2];
+      }
+      if (pmap.find(Kernel::UnitParams::twoTheta) != pmap.end()) {
+        twoTheta = pmap[Kernel::UnitParams::twoTheta];
+      }
+
+      const double sinThetaBy2 = sin(twoTheta / 2.0);
+      const double f = l1 / (l1 + l2);
+      wavelength.initialize(specInfo.l1(), 0, pmap);
       for (size_t xIndex = 0; xIndex < xLambda.size() - 1; xIndex++) {
         const double term1 = (f - 1.0) * phi1[xIndex];
         const double term2 = f * (1.0 - eps1[xIndex]);
diff --git a/Framework/Algorithms/src/CompareWorkspaces.cpp b/Framework/Algorithms/src/CompareWorkspaces.cpp
index 1a4bb7af11e..f56514c5d18 100644
--- a/Framework/Algorithms/src/CompareWorkspaces.cpp
+++ b/Framework/Algorithms/src/CompareWorkspaces.cpp
@@ -902,10 +902,12 @@ bool CompareWorkspaces::checkInstrument(
   const Geometry::ParameterMap &ws1_parmap = ws1->constInstrumentParameters();
   const Geometry::ParameterMap &ws2_parmap = ws2->constInstrumentParameters();
 
-  if (ws1_parmap != ws2_parmap) {
+  const bool checkAllData = getProperty("CheckAllData");
+  auto errorStr = ws1_parmap.diff(ws2_parmap, !checkAllData);
+  if (!errorStr.empty()) {
     g_log.debug()
         << "Here information to help understand parameter map differences:\n";
-    g_log.debug() << ws1_parmap.diff(ws2_parmap);
+    g_log.debug() << errorStr;
     recordMismatch(
         "Instrument ParameterMap mismatch (differences in ordering ignored)");
     return false;
diff --git a/Framework/Algorithms/src/ConvertSpectrumAxis.cpp b/Framework/Algorithms/src/ConvertSpectrumAxis.cpp
index 0d535494a53..e8babeb8a12 100644
--- a/Framework/Algorithms/src/ConvertSpectrumAxis.cpp
+++ b/Framework/Algorithms/src/ConvertSpectrumAxis.cpp
@@ -94,35 +94,38 @@ void ConvertSpectrumAxis::exec() {
     std::vector<double> emptyVector;
     const double l1 = spectrumInfo.l1();
     const std::string emodeStr = getProperty("EMode");
-    int emode = 0;
-    if (emodeStr == "Direct")
-      emode = 1;
-    else if (emodeStr == "Indirect")
-      emode = 2;
-    const double delta = 0.0;
-    double efixed;
+    auto emode = Kernel::DeltaEMode::fromString(emodeStr);
+    size_t nfailures = 0;
     for (size_t i = 0; i < nHist; i++) {
       std::vector<double> xval{inputWS->x(i).front(), inputWS->x(i).back()};
-      double twoTheta, l1val, l2;
-      if (!spectrumInfo.isMonitor(i)) {
-        twoTheta = spectrumInfo.twoTheta(i);
-        l2 = spectrumInfo.l2(i);
-        l1val = l1;
-        efixed =
-            getEfixed(spectrumInfo.detector(i), inputWS, emode); // get efixed
-      } else {
-        twoTheta = 0.0;
-        l2 = l1;
-        l1val = 0.0;
-        efixed = DBL_MIN;
+      UnitParametersMap pmap{};
+
+      double efixedProp = getProperty("Efixed");
+      if (efixedProp != EMPTY_DBL()) {
+        pmap[UnitParams::efixed] = efixedProp;
+        g_log.debug() << "Detector: " << spectrumInfo.detector(i).getID()
+                      << " Efixed: " << efixedProp << "\n";
+      }
+
+      spectrumInfo.getDetectorValues(*fromUnit, *toUnit, emode, false, i, pmap);
+      double value = 0.;
+      try {
+        fromUnit->toTOF(xval, emptyVector, l1, emode, pmap);
+        toUnit->fromTOF(xval, emptyVector, l1, emode, pmap);
+        value = (xval.front() + xval.back()) / 2;
+      } catch (std::runtime_error &) {
+        nfailures++;
+        g_log.warning() << "Unable to calculate new spectrum axis value for "
+                           "workspace index "
+                        << i;
+        value = inputWS->getAxis(1)->getValue(i);
       }
-      fromUnit->toTOF(xval, emptyVector, l1val, l2, twoTheta, emode, efixed,
-                      delta);
-      toUnit->fromTOF(xval, emptyVector, l1val, l2, twoTheta, emode, efixed,
-                      delta);
-      double value = (xval.front() + xval.back()) / 2;
       indexMap.emplace(value, i);
     }
+    if (nfailures == nHist) {
+      throw std::runtime_error(
+          "Unable to convert spectrum axis values on all spectra");
+    }
   } else {
     // Set up binding to memeber funtion. Avoids condition as part of loop over
     // nHistograms.
diff --git a/Framework/Algorithms/src/ConvertUnits.cpp b/Framework/Algorithms/src/ConvertUnits.cpp
index 08ece60bd6c..2b95f1f70b6 100644
--- a/Framework/Algorithms/src/ConvertUnits.cpp
+++ b/Framework/Algorithms/src/ConvertUnits.cpp
@@ -14,6 +14,7 @@
 #include "MantidDataObjects/Workspace2D.h"
 #include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidGeometry/Instrument.h"
+#include "MantidGeometry/Instrument/DetectorInfo.h"
 #include "MantidHistogramData/Histogram.h"
 #include "MantidKernel/BoundedValidator.h"
 #include "MantidKernel/CompositeValidator.h"
@@ -387,65 +388,6 @@ ConvertUnits::convertQuickly(const API::MatrixWorkspace_const_sptr &inputWS,
   return outputWS;
 }
 
-/** Get the L2, theta and efixed values for a workspace index
- * @param spectrumInfo :: SpectrumInfo of the workspace
- * @param outputUnit :: The output unit
- * @param emode :: The energy mode
- * @param ws :: The workspace
- * @param signedTheta :: Return twotheta with sign or without
- * @param wsIndex :: The workspace index
- * @param efixed :: the returned fixed energy
- * @param l2 :: The returned sample - detector distance
- * @param twoTheta :: the returned two theta angle
- * @returns true if lookup successful, false on error
- */
-bool ConvertUnits::getDetectorValues(const API::SpectrumInfo &spectrumInfo,
-                                     const Kernel::Unit &outputUnit, int emode,
-                                     const MatrixWorkspace &ws,
-                                     const bool signedTheta, int64_t wsIndex,
-                                     double &efixed, double &l2,
-                                     double &twoTheta) {
-  if (!spectrumInfo.hasDetectors(wsIndex))
-    return false;
-
-  l2 = spectrumInfo.l2(wsIndex);
-
-  if (!spectrumInfo.isMonitor(wsIndex)) {
-    // The scattering angle for this detector (in radians).
-    try {
-      if (signedTheta)
-        twoTheta = spectrumInfo.signedTwoTheta(wsIndex);
-      else
-        twoTheta = spectrumInfo.twoTheta(wsIndex);
-    } catch (const std::runtime_error &e) {
-      g_log.warning(e.what());
-      twoTheta = std::numeric_limits<double>::quiet_NaN();
-    }
-    // If an indirect instrument, try getting Efixed from the geometry
-    if (emode == 2 && efixed == EMPTY_DBL()) // indirect
-    {
-      if (spectrumInfo.hasUniqueDetector(wsIndex)) {
-        const auto &det = spectrumInfo.detector(wsIndex);
-        auto par = ws.constInstrumentParameters().getRecursive(&det, "Efixed");
-        if (par) {
-          efixed = par->value<double>();
-          g_log.debug() << "Detector: " << det.getID() << " EFixed: " << efixed
-                        << "\n";
-        }
-      }
-      // Non-unique detector (i.e., DetectorGroup): use single provided value
-    }
-  } else {
-    twoTheta = 0.0;
-    efixed = DBL_MIN;
-    // Energy transfer is meaningless for a monitor, so set l2 to 0.
-    if (outputUnit.unitID().find("DeltaE") != std::string::npos) {
-      l2 = 0.0;
-    }
-  }
-  return true;
-}
-
 /** Convert the workspace units using TOF as an intermediate step in the
  * conversion
  * @param fromUnit :: The unit of the input workspace
@@ -472,44 +414,18 @@ ConvertUnits::convertViaTOF(Kernel::Unit_const_sptr fromUnit,
   /// @todo No implementation for any of these in the geometry yet so using
   /// properties
   const std::string emodeStr = getProperty("EMode");
-  // Convert back to an integer representation
-  int emode = 0;
-  if (emodeStr == "Direct")
-    emode = 1;
-  else if (emodeStr == "Indirect")
-    emode = 2;
+  DeltaEMode::Type emode = DeltaEMode::fromString(emodeStr);
 
   // Not doing anything with the Y vector in to/fromTOF yet, so just pass
   // empty
   // vector
   std::vector<double> emptyVec;
-  const bool needEfixed =
-      (outputUnit->unitID().find("DeltaE") != std::string::npos ||
-       outputUnit->unitID().find("Wave") != std::string::npos);
   double efixedProp = getProperty("Efixed");
-  if (emode == 1) {
-    //... direct efixed gather
-    if (efixedProp == EMPTY_DBL()) {
-      // try and get the value from the run parameters
-      const API::Run &run = inputWS->run();
-      if (run.hasProperty("Ei")) {
-        try {
-          efixedProp = run.getPropertyValueAsType<double>("Ei");
-        } catch (Kernel::Exception::NotFoundError &) {
-          throw std::runtime_error("Cannot retrieve Ei value from the logs");
-        }
-      } else {
-        if (needEfixed) {
-          throw std::invalid_argument(
-              "Could not retrieve incident energy from run object");
-        } else {
-          efixedProp = 0.0;
-        }
-      }
+  if (efixedProp == EMPTY_DBL() && emode == DeltaEMode::Type::Direct) {
+    try {
+      efixedProp = inputWS->getEFixedGivenEMode(nullptr, emode);
+    } catch (std::runtime_error &) {
     }
-  } else if (emode == 0 && efixedProp == EMPTY_DBL()) // Elastic
-  {
-    efixedProp = 0.0;
   }
 
   std::vector<std::string> parameters =
@@ -518,25 +434,27 @@ ConvertUnits::convertViaTOF(Kernel::Unit_const_sptr fromUnit,
       (!parameters.empty()) &&
       find(parameters.begin(), parameters.end(), "Always") != parameters.end();
 
-  auto localFromUnit = std::unique_ptr<Unit>(fromUnit->clone());
-  auto localOutputUnit = std::unique_ptr<Unit>(outputUnit->clone());
+  auto checkFromUnit = std::unique_ptr<Unit>(fromUnit->clone());
+  auto checkOutputUnit = std::unique_ptr<Unit>(outputUnit->clone());
 
   // Perform Sanity Validation before creating workspace
-  double checkefixed = efixedProp;
-  double checkl2;
-  double checktwoTheta;
+  const double checkdelta = 0.0;
+  UnitParametersMap pmap = {{UnitParams::delta, checkdelta}};
+  if (efixedProp != EMPTY_DBL()) {
+    pmap[UnitParams::efixed] = efixedProp;
+  }
   size_t checkIndex = 0;
-  if (getDetectorValues(spectrumInfo, *outputUnit, emode, *inputWS, signedTheta,
-                        checkIndex, checkefixed, checkl2, checktwoTheta)) {
-    const double checkdelta = 0.0;
-    // copy the X values for the check
-    auto checkXValues = inputWS->readX(checkIndex);
+  spectrumInfo.getDetectorValues(*fromUnit, *outputUnit, emode, signedTheta,
+                                 checkIndex, pmap);
+  // copy the X values for the check
+  auto checkXValues = inputWS->readX(checkIndex);
+  try {
     // Convert the input unit to time-of-flight
-    localFromUnit->toTOF(checkXValues, emptyVec, l1, checkl2, checktwoTheta,
-                         emode, checkefixed, checkdelta);
+    checkFromUnit->toTOF(checkXValues, emptyVec, l1, emode, pmap);
     // Convert from time-of-flight to the desired unit
-    localOutputUnit->fromTOF(checkXValues, emptyVec, l1, checkl2, checktwoTheta,
-                             emode, checkefixed, checkdelta);
+    checkOutputUnit->fromTOF(checkXValues, emptyVec, l1, emode, pmap);
+  } catch (
+      std::runtime_error) { // if it's a detector specific problem then ignore
   }
 
   // create the output workspace
@@ -547,32 +465,38 @@ ConvertUnits::convertViaTOF(Kernel::Unit_const_sptr fromUnit,
 
   auto &outSpectrumInfo = outputWS->mutableSpectrumInfo();
   // Loop over the histograms (detector spectra)
+  PARALLEL_FOR_IF(Kernel::threadSafe(*outputWS))
   for (int64_t i = 0; i < numberOfSpectra_i; ++i) {
+    PARALLEL_START_INTERUPT_REGION
     double efixed = efixedProp;
 
     // Now get the detector object for this histogram
-    double l2;
-    double twoTheta;
-    if (getDetectorValues(outSpectrumInfo, *outputUnit, emode, *outputWS,
-                          signedTheta, i, efixed, l2, twoTheta)) {
+    /// @todo Don't yet consider hold-off (delta)
+    const double delta = 0.0;
 
-      /// @todo Don't yet consider hold-off (delta)
-      const double delta = 0.0;
+    auto localFromUnit = std::unique_ptr<Unit>(fromUnit->clone());
+    auto localOutputUnit = std::unique_ptr<Unit>(outputUnit->clone());
 
-      // TODO toTOF and fromTOF need to be reimplemented outside of kernel
-      localFromUnit->toTOF(outputWS->dataX(i), emptyVec, l1, l2, twoTheta,
-                           emode, efixed, delta);
+    // TODO toTOF and fromTOF need to be reimplemented outside of kernel
+    UnitParametersMap pmap = {{UnitParams::delta, delta}};
+    if (efixedProp != EMPTY_DBL()) {
+      pmap[UnitParams::efixed] = efixed;
+    }
+    outSpectrumInfo.getDetectorValues(*fromUnit, *outputUnit, emode,
+                                      signedTheta, i, pmap);
+    try {
+      localFromUnit->toTOF(outputWS->dataX(i), emptyVec, l1, emode, pmap);
       // Convert from time-of-flight to the desired unit
-      localOutputUnit->fromTOF(outputWS->dataX(i), emptyVec, l1, l2, twoTheta,
-                               emode, efixed, delta);
+      localOutputUnit->fromTOF(outputWS->dataX(i), emptyVec, l1, emode, pmap);
 
       // EventWorkspace part, modifying the EventLists.
       if (m_inputEvents) {
         eventWS->getSpectrum(i).convertUnitsViaTof(localFromUnit.get(),
                                                    localOutputUnit.get());
       }
-    } else {
-      // Get to here if exception thrown when calculating distance to detector
+    } catch (std::runtime_error) {
+      // Get to here if exception thrown in unit conversion eg when calculating
+      // distance to detector
       failedDetectorCount++;
       // Since you usually (always?) get to here when there's no attached
       // detectors, this call is
@@ -584,7 +508,9 @@ ConvertUnits::convertViaTOF(Kernel::Unit_const_sptr fromUnit,
     }
 
     prog.report("Convert to " + m_outputUnit->unitID());
+    PARALLEL_END_INTERUPT_REGION
   } // loop over spectra
+  PARALLEL_CHECK_INTERUPT_REGION
 
   if (failedDetectorCount != 0) {
     g_log.information() << "Unable to calculate sample-detector distance for "
diff --git a/Framework/Algorithms/src/ConvertUnitsUsingDetectorTable.cpp b/Framework/Algorithms/src/ConvertUnitsUsingDetectorTable.cpp
index 70aa822f327..01e5ac7d1a1 100644
--- a/Framework/Algorithms/src/ConvertUnitsUsingDetectorTable.cpp
+++ b/Framework/Algorithms/src/ConvertUnitsUsingDetectorTable.cpp
@@ -162,16 +162,14 @@ MatrixWorkspace_sptr ConvertUnitsUsingDetectorTable::convertViaTOF(
     // Convert the input unit to time-of-flight
     auto checkFromUnit = std::unique_ptr<Unit>(fromUnit->clone());
     auto checkOutputUnit = std::unique_ptr<Unit>(outputUnit->clone());
-    double checkdelta = 0;
+    UnitParametersMap pmap{{UnitParams::l2, l2Column[detectorRow]},
+                           {UnitParams::twoTheta, twoThetaColumn[detectorRow]},
+                           {UnitParams::efixed, efixedColumn[detectorRow]}};
     checkFromUnit->toTOF(checkXValues, emptyVec, l1Column[detectorRow],
-                         l2Column[detectorRow], twoThetaColumn[detectorRow],
-                         emodeColumn[detectorRow], efixedColumn[detectorRow],
-                         checkdelta);
+                         emodeColumn[detectorRow], pmap);
     // Convert from time-of-flight to the desired unit
     checkOutputUnit->fromTOF(checkXValues, emptyVec, l1Column[detectorRow],
-                             l2Column[detectorRow], twoThetaColumn[detectorRow],
-                             emodeColumn[detectorRow],
-                             efixedColumn[detectorRow], checkdelta);
+                             emodeColumn[detectorRow], pmap);
   }
 
   // create the output workspace
@@ -229,16 +227,16 @@ MatrixWorkspace_sptr ConvertUnitsUsingDetectorTable::convertViaTOF(
         auto localFromUnit = std::unique_ptr<Unit>(fromUnit->clone());
         auto localOutputUnit = std::unique_ptr<Unit>(outputUnit->clone());
         /// @todo Don't yet consider hold-off (delta)
-        const double delta = 0.0;
         std::vector<double> values(outputWS->x(wsid).begin(),
                                    outputWS->x(wsid).end());
 
+        UnitParametersMap pmap{{UnitParams::l2, l2},
+                               {UnitParams::twoTheta, twoTheta},
+                               {UnitParams::efixed, efixed}};
         // Convert the input unit to time-of-flight
-        localFromUnit->toTOF(values, emptyVec, l1, l2, twoTheta, emode, efixed,
-                             delta);
+        localFromUnit->toTOF(values, emptyVec, l1, emode, pmap);
         // Convert from time-of-flight to the desired unit
-        localOutputUnit->fromTOF(values, emptyVec, l1, l2, twoTheta, emode,
-                                 efixed, delta);
+        localOutputUnit->fromTOF(values, emptyVec, l1, emode, pmap);
 
         outputWS->mutableX(wsid) = std::move(values);
 
diff --git a/Framework/Algorithms/src/CreateDetectorTable.cpp b/Framework/Algorithms/src/CreateDetectorTable.cpp
index a466b80a733..23029e752fc 100644
--- a/Framework/Algorithms/src/CreateDetectorTable.cpp
+++ b/Framework/Algorithms/src/CreateDetectorTable.cpp
@@ -147,8 +147,15 @@ createDetectorTableWorkspace(const MatrixWorkspace_sptr &ws,
     calcQ = false;
   }
 
+  bool hasDiffConstants{false};
+  auto emode = ws->getEMode();
+  if (emode == DeltaEMode::Elastic) {
+    hasDiffConstants = true;
+  }
+
   // Prepare column names
-  auto colNames = createColumns(isScanning, includeData, calcQ);
+  auto colNames =
+      createColumns(isScanning, includeData, calcQ, hasDiffConstants);
 
   const int ncols = static_cast<int>(colNames.size());
   const int nrows = indices.empty()
@@ -175,13 +182,14 @@ createDetectorTableWorkspace(const MatrixWorkspace_sptr &ws,
 
   populateTable(t, ws, nrows, indices, spectrumInfo, signedThetaParamRetrieved,
                 showSignedTwoTheta, beamAxisIndex, sampleDist, isScanning,
-                includeData, calcQ, logger);
+                includeData, calcQ, hasDiffConstants, logger);
 
   return t;
 }
 
 std::vector<std::pair<std::string, std::string>>
-createColumns(const bool isScanning, const bool includeData, const bool calcQ) {
+createColumns(const bool isScanning, const bool includeData, const bool calcQ,
+              const bool hasDiffConstants) {
   std::vector<std::pair<std::string, std::string>> colNames;
   colNames.emplace_back("double", "Index");
   colNames.emplace_back("int", "Spectrum No");
@@ -200,6 +208,12 @@ createColumns(const bool isScanning, const bool includeData, const bool calcQ) {
   }
   colNames.emplace_back("double", "Phi");
   colNames.emplace_back("str", "Monitor");
+  if (hasDiffConstants) {
+    colNames.emplace_back("double", "DIFA");
+    colNames.emplace_back("double", "DIFC");
+    colNames.emplace_back("double", "DIFC - Uncalibrated");
+    colNames.emplace_back("double", "TZERO");
+  }
   return colNames;
 }
 
@@ -209,7 +223,8 @@ void populateTable(ITableWorkspace_sptr &t, const MatrixWorkspace_sptr &ws,
                    bool signedThetaParamRetrieved, bool showSignedTwoTheta,
                    const PointingAlong &beamAxisIndex, const double sampleDist,
                    const bool isScanning, const bool includeData,
-                   const bool calcQ, Logger &logger) {
+                   const bool calcQ, const bool includeDiffConstants,
+                   Logger &logger) {
   PARALLEL_FOR_IF(Mantid::Kernel::threadSafe(*ws))
   for (int row = 0; row < nrows; ++row) {
     TableRow colValues = t->getRow(row);
@@ -307,7 +322,22 @@ void populateTable(ITableWorkspace_sptr &t, const MatrixWorkspace_sptr &ws,
 
       colValues << phi               // rtp
                 << isMonitorDisplay; // monitor
+
+      if (includeDiffConstants) {
+        if (isMonitor) {
+          colValues << 0. << 0. << 0. << 0.;
+        } else {
+          auto diffConsts = spectrumInfo.diffractometerConstants(wsIndex);
+          auto difcValueUncalibrated = spectrumInfo.difcUncalibrated(wsIndex);
+          // map will create an entry with zero value if not present already
+          colValues << diffConsts[UnitParams::difa]
+                    << diffConsts[UnitParams::difc] << difcValueUncalibrated
+                    << diffConsts[UnitParams::tzero];
+        }
+      }
     } catch (const std::exception &) {
+      colValues.row(row);
+      colValues << static_cast<double>(wsIndex);
       // spectrumNo=-1, detID=0
       colValues << -1 << "0";
       // Y/E
@@ -320,7 +350,10 @@ void populateTable(ITableWorkspace_sptr &t, const MatrixWorkspace_sptr &ws,
       }
       colValues << 0.0    // rtp
                 << "n/a"; // monitor
-    }                     // End catch for no spectrum
+      if (includeDiffConstants) {
+        colValues << 0.0 << 0.0 << 0.0 << 0.0;
+      }
+    } // End catch for no spectrum
   }
 }
 
diff --git a/Framework/Algorithms/src/GetAllEi.cpp b/Framework/Algorithms/src/GetAllEi.cpp
index e7a6d1fc263..498fd7852c1 100644
--- a/Framework/Algorithms/src/GetAllEi.cpp
+++ b/Framework/Algorithms/src/GetAllEi.cpp
@@ -255,7 +255,6 @@ void GetAllEi::exec() {
       (0.5 * 1.e+6) / chopSpeed; // 0.5 because some choppers open twice.
   // Would be nice to have it 1 or 0.5 depending on chopper type, but
   // it looks like not enough information on what chopper is available on ws;
-  double unused(0.0);
   auto destUnit = Kernel::UnitFactory::Instance().create("Energy");
 
   std::vector<double> guess_opening;
@@ -267,9 +266,8 @@ void GetAllEi::exec() {
         boost::lexical_cast<std::string>(TOF_range.first) + ':' +
         boost::lexical_cast<std::string>(TOF_range.second));
   } else {
-    destUnit->initialize(mon1Distance, 0., 0.,
-                         static_cast<int>(Kernel::DeltaEMode::Elastic), 0.,
-                         unused);
+    destUnit->initialize(mon1Distance,
+                         static_cast<int>(Kernel::DeltaEMode::Elastic), {});
     printDebugModeInfo(guess_opening, TOF_range, destUnit);
   }
   std::pair<double, double> Mon1_Erange =
@@ -286,9 +284,8 @@ void GetAllEi::exec() {
   // convert to energy
   std::vector<double> guess_ei;
   guess_ei.reserve(guess_opening.size());
-  destUnit->initialize(mon1Distance, 0., 0.,
-                       static_cast<int>(Kernel::DeltaEMode::Elastic), 0.,
-                       unused);
+  destUnit->initialize(mon1Distance,
+                       static_cast<int>(Kernel::DeltaEMode::Elastic), {});
   for (double time : guess_opening) {
     double eGuess = destUnit->singleFromTOF(time);
     if (eGuess > eMin && eGuess < eMax) {
diff --git a/Framework/Algorithms/src/PDCalibration.cpp b/Framework/Algorithms/src/PDCalibration.cpp
index 09813e1df37..61829c36ffa 100644
--- a/Framework/Algorithms/src/PDCalibration.cpp
+++ b/Framework/Algorithms/src/PDCalibration.cpp
@@ -29,10 +29,10 @@
 #include "MantidKernel/ArrayProperty.h"
 #include "MantidKernel/BoundedValidator.h"
 #include "MantidKernel/CompositeValidator.h"
-#include "MantidKernel/Diffraction.h"
 #include "MantidKernel/ListValidator.h"
 #include "MantidKernel/MandatoryValidator.h"
 #include "MantidKernel/RebinParamsValidator.h"
+#include "MantidKernel/Unit.h"
 
 #include <algorithm>
 #include <cassert>
@@ -120,11 +120,13 @@ public:
    *
    * @param peaksInD :: peak centers, in d-spacing
    * @param peaksInDWindows :: left and right fit ranges for each peak
-   * @param toTof :: function converting from d-spacing to TOF
+   * @param difa :: difa diffractometer constant (quadratic term)
+   * @param difc :: difc diffractometer constant (linear term)
+   * @param tzero :: tzero diffractometer constant (constant term)
    */
   void setPositions(const std::vector<double> &peaksInD,
                     const std::vector<double> &peaksInDWindows,
-                    const std::function<double(double)> &toTof) {
+                    const double difa, const double difc, const double tzero) {
     // clear out old values
     inDPos.clear();
     inTofPos.clear();
@@ -136,9 +138,16 @@ public:
     inTofWindows.assign(peaksInDWindows.begin(), peaksInDWindows.end());
 
     // convert the bits that matter to TOF
-    std::transform(inTofPos.begin(), inTofPos.end(), inTofPos.begin(), toTof);
-    std::transform(inTofWindows.begin(), inTofWindows.end(),
-                   inTofWindows.begin(), toTof);
+    Kernel::Units::dSpacing dSpacingUnit;
+    std::vector<double> yunused;
+    dSpacingUnit.toTOF(inTofPos, yunused, -1, 0,
+                       {{Kernel::UnitParams::difa, difa},
+                        {Kernel::UnitParams::difc, difc},
+                        {Kernel::UnitParams::tzero, tzero}});
+    dSpacingUnit.toTOF(inTofWindows, yunused, -1, 0,
+                       {{Kernel::UnitParams::difa, difa},
+                        {Kernel::UnitParams::difc, difc},
+                        {Kernel::UnitParams::tzero, tzero}});
   }
 
   std::size_t wkspIndex;
@@ -581,8 +590,10 @@ void PDCalibration::exec() {
      // object to hold the information about the peak positions, detid, and wksp
      // index
      PDCalibration::FittedPeaks peaks(m_uncalibratedWS, wkspIndex);
-     auto toTof = getDSpacingToTof(peaks.detid);
-     peaks.setPositions(m_peaksInDspacing, windowsInDSpacing, toTof);
+     auto [difc, difa, tzero] = getDSpacingToTof(
+         peaks.detid); // doesn't matter which one - all have same difc etc.
+     peaks.setPositions(m_peaksInDspacing, windowsInDSpacing, difa, difc,
+                        tzero);
 
      // includes peaks that aren't used in the fit
      // The following data structures will hold information for the peaks
@@ -690,15 +701,18 @@ void PDCalibration::exec() {
          // and the peak positions using the GSAS formula with optimized difc,
          // difa, and tzero
          double chisq = 0.;
-         // `converter` if a function that returns a d-spacing for an input TOF
-         auto converter =
-             Kernel::Diffraction::getTofToDConversionFunc(difc, difa, t0);
+         Mantid::Kernel::Units::dSpacing dSpacingUnit;
+         dSpacingUnit.initialize(
+             -1., 0,
+             Kernel::UnitParametersMap{{Kernel::UnitParams::difa, difa},
+                                       {Kernel::UnitParams::difc, difc},
+                                       {Kernel::UnitParams::tzero, t0}});
          for (std::size_t i = 0; i < numPeaks; ++i) {
            if (std::isnan(tof_vec_full[i]))
              continue;
            // Find d-spacing using the GSAS formula with optimized difc, difa,
            // t0 for the TOF of the current peak's center.
-           const double dspacing = converter(tof_vec_full[i]);
+           const double dspacing = dSpacingUnit.singleFromTOF(tof_vec_full[i]);
            // `temp` is residual between the nominal position in d-spacing for
            // the current peak, and the fitted position in d-spacing
            const double temp = m_peaksInDspacing[i] - dspacing;
@@ -706,7 +720,7 @@ void PDCalibration::exec() {
            m_peakPositionTable->cell<double>(rowIndexOutputPeaks, i + 1) =
                dspacing;
            m_peakWidthTable->cell<double>(rowIndexOutputPeaks, i + 1) =
-               WIDTH_TO_FWHM * converter(width_vec_full[i]);
+               WIDTH_TO_FWHM * dSpacingUnit.singleFromTOF(width_vec_full[i]);
            m_peakHeightTable->cell<double>(rowIndexOutputPeaks, i + 1) =
                height_vec_full[i];
          }
@@ -804,13 +818,17 @@ double gsl_costFunction(const gsl_vector *v, void *peaks) {
     if (numParams > 2)
       difa = gsl_vector_get(v, 2);
   }
-  auto converter =
-      Kernel::Diffraction::getDToTofConversionFunc(difc, difa, tzero);
+  Mantid::Kernel::Units::dSpacing dSpacingUnit;
+  dSpacingUnit.initialize(
+      -1., 0,
+      Kernel::UnitParametersMap{{Kernel::UnitParams::difa, difa},
+                                {Kernel::UnitParams::difc, difc},
+                                {Kernel::UnitParams::tzero, tzero}});
 
   // calculate the sum of the residuals from observed peaks
   double errsum = 0.0;
   for (size_t i = 0; i < numPeaks; ++i) {
-    const double tofCalib = converter(dspace[i]);
+    const double tofCalib = dSpacingUnit.singleToTOF(dspace[i]);
     const double errsum_i = std::fabs(tofObs[i] - tofCalib) * weights[i];
     errsum += errsum_i;
   }
@@ -1056,9 +1074,8 @@ PDCalibration::dSpacingWindows(const std::vector<double> &centres,
  *
  * @param detIds :: set of detector IDs
  */
-std::function<double(double)>
+std::tuple<double, double, double>
 PDCalibration::getDSpacingToTof(const std::set<detid_t> &detIds) {
-
   // to start this is the old calibration values
   double difc = 0.;
   double difa = 0.;
@@ -1076,7 +1093,7 @@ PDCalibration::getDSpacingToTof(const std::set<detid_t> &detIds) {
     tzero = norm * tzero;
   }
 
-  return Kernel::Diffraction::getDToTofConversionFunc(difc, difa, tzero);
+  return {difc, difa, tzero};
 }
 
 void PDCalibration::setCalibrationValues(const detid_t detid, const double difc,
@@ -1111,8 +1128,9 @@ vector<double> PDCalibration::getTOFminmax(const double difc, const double difa,
                                            const double tzero) {
   vector<double> tofminmax(2);
 
-  tofminmax[0] = Kernel::Diffraction::calcTofMin(difc, difa, tzero, m_tofMin);
-  tofminmax[1] = Kernel::Diffraction::calcTofMax(difc, difa, tzero, m_tofMax);
+  Kernel::Units::dSpacing dSpacingUnit;
+  tofminmax[0] = dSpacingUnit.calcTofMin(difc, difa, tzero, m_tofMin);
+  tofminmax[1] = dSpacingUnit.calcTofMax(difc, difa, tzero, m_tofMax);
 
   return tofminmax;
 }
@@ -1433,9 +1451,9 @@ PDCalibration::createTOFPeakCenterFitWindowWorkspaces(
     PDCalibration::FittedPeaks peaks(dataws, static_cast<size_t>(iws));
     // toTof is a function that converts from d-spacing to TOF for a particular
     // pixel
-    auto toTof = getDSpacingToTof(peaks.detid);
+    auto [difc, difa, tzero] = getDSpacingToTof(peaks.detid);
     // setpositions initializes peaks.inTofPos and peaks.inTofWindows
-    peaks.setPositions(m_peaksInDspacing, windowsInDSpacing, toTof);
+    peaks.setPositions(m_peaksInDspacing, windowsInDSpacing, difa, difc, tzero);
     peak_pos_ws->setPoints(iws, peaks.inTofPos);
     peak_window_ws->setPoints(iws, peaks.inTofWindows);
     prog.report();
diff --git a/Framework/Algorithms/src/RemoveBackground.cpp b/Framework/Algorithms/src/RemoveBackground.cpp
index 2bda22e3fd5..f8e6c5268b7 100644
--- a/Framework/Algorithms/src/RemoveBackground.cpp
+++ b/Framework/Algorithms/src/RemoveBackground.cpp
@@ -106,9 +106,8 @@ void RemoveBackground::exec() {
                                 "workspace");
   }
 
-  int eMode; // in convert units emode is still integer
   const std::string emodeStr = getProperty("EMode");
-  eMode = static_cast<int>(Kernel::DeltaEMode::fromString(emodeStr));
+  auto eMode = Kernel::DeltaEMode::fromString(emodeStr);
 
   // Removing background in-place
   bool inPlace = (inputWS == outputWS);
@@ -155,7 +154,7 @@ void RemoveBackground::exec() {
 BackgroundHelper::BackgroundHelper()
     : m_WSUnit(), m_bgWs(), m_wkWS(), m_spectrumInfo(nullptr), m_pgLog(nullptr),
       m_inPlace(true), m_singleValueBackground(false), m_NBg(0), m_dtBg(1),
-      m_ErrSq(0), m_Emode(0), m_Efix(0), m_nullifyNegative(false),
+      m_ErrSq(0), m_Emode(DeltaEMode::Elastic), m_nullifyNegative(false),
       m_previouslyRemovedBkgMode(false) {}
 
 /** Initialization method:
@@ -172,7 +171,8 @@ or target workspace has to be cloned.
 */
 void BackgroundHelper::initialize(const API::MatrixWorkspace_const_sptr &bkgWS,
                                   const API::MatrixWorkspace_sptr &sourceWS,
-                                  int emode, Kernel::Logger *pLog, int nThreads,
+                                  Kernel::DeltaEMode::Type emode,
+                                  Kernel::Logger *pLog, int nThreads,
                                   bool inPlace, bool nullifyNegative) {
   m_bgWs = bkgWS;
   m_wkWS = sourceWS;
@@ -224,8 +224,6 @@ void BackgroundHelper::initialize(const API::MatrixWorkspace_const_sptr &bkgWS,
     if (m_NBg < 1.e-7 && m_ErrSq < 1.e-7)
       m_previouslyRemovedBkgMode = true;
   }
-
-  m_Efix = this->getEi(sourceWS);
 }
 /**Method removes background from vectors which represent a histogram data for a
  * single spectra
@@ -257,10 +255,8 @@ void BackgroundHelper::removeBackground(int nHist, HistogramX &x_data,
   }
 
   try {
-    double twoTheta = m_spectrumInfo->twoTheta(nHist);
     double L1 = m_spectrumInfo->l1();
-    double L2 = m_spectrumInfo->l2(nHist);
-    double delta(std::numeric_limits<double>::quiet_NaN());
+
     // get access to source workspace in case if target is different from source
     auto &XValues = m_wkWS->x(nHist);
     auto &YValues = m_wkWS->y(nHist);
@@ -268,7 +264,10 @@ void BackgroundHelper::removeBackground(int nHist, HistogramX &x_data,
 
     // use thread-specific unit conversion class to avoid multithreading issues
     Kernel::Unit *unitConv = m_WSUnit[threadNum].get();
-    unitConv->initialize(L1, L2, twoTheta, m_Emode, m_Efix, delta);
+    Kernel::UnitParametersMap pmap{};
+    m_spectrumInfo->getDetectorValues(*unitConv, Kernel::Units::TOF{}, m_Emode,
+                                      false, nHist, pmap);
+    unitConv->initialize(L1, m_Emode, pmap);
 
     x_data[0] = XValues[0];
     double tof1 = unitConv->singleToTOF(x_data[0]);
@@ -315,46 +314,6 @@ void BackgroundHelper::removeBackground(int nHist, HistogramX &x_data,
           << nHist;
   }
 }
-/** Method returns the efixed or Ei value stored in properties of the input
- *workspace.
- *  Indirect instruments can have eFxed and Direct instruments can have Ei
- *defined as the properties of the workspace.
- *
- *  This method provide guess for efixed for all other kind of instruments.
- *Correct indirect instrument will overwrite
- *  this value while wrongly defined or different types of instruments will
- *provide the value of "Ei" property (log value)
- *  or undefined if "Ei" property is not found.
- *
- */
-double
-BackgroundHelper::getEi(const API::MatrixWorkspace_const_sptr &inputWS) const {
-  double Efi = std::numeric_limits<double>::quiet_NaN();
-
-  // is Ei on workspace properties? (it can be defined for some reason if
-  // detectors do not have one, and then it would exist as Ei)
-  bool EiFound(false);
-  try {
-    Efi = inputWS->run().getPropertyValueAsType<double>("Ei");
-    EiFound = true;
-  } catch (Kernel::Exception::NotFoundError &) {
-  }
-  // try to get Efixed as property on a workspace, obtained for indirect
-  // instrument
-  // bool eFixedFound(false);
-  if (!EiFound) {
-    try {
-      Efi = inputWS->run().getPropertyValueAsType<double>("eFixed");
-      // eFixedFound = true;
-    } catch (Kernel::Exception::NotFoundError &) {
-    }
-  }
-
-  // if (!(EiFound||eFixedFound))
-  //  g_log.debug()<<" Ei/eFixed requested but have not been found\n";
-
-  return Efi;
-}
 
 } // namespace Algorithms
 } // namespace Mantid
diff --git a/Framework/Algorithms/src/RemoveBins.cpp b/Framework/Algorithms/src/RemoveBins.cpp
index 1024f935fe3..c27b740c4d3 100644
--- a/Framework/Algorithms/src/RemoveBins.cpp
+++ b/Framework/Algorithms/src/RemoveBins.cpp
@@ -268,16 +268,36 @@ void RemoveBins::transformRangeUnit(const int index, double &startX,
     startX = factor * std::pow(m_startX, power);
     endX = factor * std::pow(m_endX, power);
   } else {
-    double l1, l2, theta;
-    this->calculateDetectorPosition(index, l1, l2, theta);
+    double l1 = m_spectrumInfo->l1();
+
+    Kernel::UnitParametersMap pmap{};
+    m_spectrumInfo->getDetectorValues(*m_rangeUnit, *inputUnit,
+                                      Kernel::DeltaEMode::Elastic, false, index,
+                                      pmap);
+    double l2 = 0.;
+    if (pmap.find(UnitParams::l2) != pmap.end()) {
+      l2 = pmap[UnitParams::l2];
+    }
+    double theta = 0.;
+    if (pmap.find(UnitParams::twoTheta) != pmap.end()) {
+      l2 = pmap[UnitParams::twoTheta];
+    }
+    g_log.debug() << "Detector for index " << index << " has L1+L2=" << l1 + l2
+                  << " & 2theta= " << theta << '\n';
     std::vector<double> endPoints;
     endPoints.emplace_back(startX);
     endPoints.emplace_back(endX);
-    std::vector<double> emptyVec;
-    m_rangeUnit->toTOF(endPoints, emptyVec, l1, l2, theta, 0, 0.0, 0.0);
-    inputUnit->fromTOF(endPoints, emptyVec, l1, l2, theta, 0, 0.0, 0.0);
-    startX = endPoints.front();
-    endX = endPoints.back();
+    try {
+      std::vector<double> emptyVec;
+      // assume elastic
+      m_rangeUnit->toTOF(endPoints, emptyVec, l1, 0, pmap);
+      inputUnit->fromTOF(endPoints, emptyVec, l1, 0, pmap);
+      startX = endPoints.front();
+      endX = endPoints.back();
+    } catch (std::exception &) {
+      throw std::runtime_error("Unable to retrieve detector properties "
+                               "required for unit conversion");
+    }
   }
 
   if (startX > endX) {
@@ -290,25 +310,6 @@ void RemoveBins::transformRangeUnit(const int index, double &startX,
                 << startX << "-" << endX << " in workspace's unit\n";
 }
 
-/** Retrieves the detector postion for a given spectrum
- *  @param index ::    The workspace index of the spectrum
- *  @param l1 ::       Returns the source-sample distance
- *  @param l2 ::       Returns the sample-detector distance
- *  @param twoTheta :: Returns the detector's scattering angle
- */
-void RemoveBins::calculateDetectorPosition(const int index, double &l1,
-                                           double &l2, double &twoTheta) {
-  l1 = m_spectrumInfo->l1();
-  l2 = m_spectrumInfo->l2(index);
-  if (m_spectrumInfo->isMonitor(index))
-    twoTheta = 0.0;
-  else
-    twoTheta = m_spectrumInfo->twoTheta(index);
-
-  g_log.debug() << "Detector for index " << index << " has L1+L2=" << l1 + l2
-                << " & 2theta= " << twoTheta << '\n';
-}
-
 /** Finds the index in an ordered vector which follows the given value
  *  @param value :: The value to search for
  *  @param vec ::   The vector to search
diff --git a/Framework/Algorithms/src/RemoveLowResTOF.cpp b/Framework/Algorithms/src/RemoveLowResTOF.cpp
index e829ec0fbe4..50a2e71bc50 100644
--- a/Framework/Algorithms/src/RemoveLowResTOF.cpp
+++ b/Framework/Algorithms/src/RemoveLowResTOF.cpp
@@ -246,17 +246,9 @@ double RemoveLowResTOF::calcTofMin(const std::size_t workspaceIndex,
 
   const double l1 = spectrumInfo.l1();
 
-  // Get a vector of detector IDs
-  std::vector<detid_t> detNumbers;
-  const auto &detSet = m_inputWS->getSpectrum(workspaceIndex).getDetectorIDs();
-  detNumbers.assign(detSet.begin(), detSet.end());
-
   double tmin = 0.;
   if (isEmpty(m_wavelengthMin)) {
-    std::map<detid_t, double> offsets; // just an empty offsets map
-    double dspmap = Conversion::tofToDSpacingFactor(
-        l1, spectrumInfo.l2(workspaceIndex),
-        spectrumInfo.twoTheta(workspaceIndex), detNumbers, offsets);
+    double dspmap = 1. / spectrumInfo.difcUncalibrated(workspaceIndex);
 
     // this is related to the reference tof
     double sqrtdmin =
@@ -274,7 +266,7 @@ double RemoveLowResTOF::calcTofMin(const std::size_t workspaceIndex,
     // unfortunately there isn't a good way to convert a single value
     std::vector<double> X(1), temp(1);
     X[0] = m_wavelengthMin;
-    wavelength->toTOF(X, temp, l1, l2, 0., 0, 0., 0.);
+    wavelength->toTOF(X, temp, l1, 0, {{UnitParams::l2, l2}});
     tmin = X[0];
   }
 
diff --git a/Framework/Algorithms/test/ConvertSpectrumAxisTest.h b/Framework/Algorithms/test/ConvertSpectrumAxisTest.h
index fcab91851b1..c6d9e4adffe 100644
--- a/Framework/Algorithms/test/ConvertSpectrumAxisTest.h
+++ b/Framework/Algorithms/test/ConvertSpectrumAxisTest.h
@@ -142,7 +142,7 @@ public:
     TS_ASSERT_THROWS_NOTHING(conv.setPropertyValue("Target", "DeltaE"));
     TS_ASSERT_THROWS_NOTHING(conv.setPropertyValue("EMode", "Indirect"));
     conv.setRethrows(true);
-    TS_ASSERT_THROWS(conv.execute(), const std::logic_error &);
+    TS_ASSERT_THROWS(conv.execute(), const std::runtime_error &);
 
     TS_ASSERT_THROWS_NOTHING(conv.setPropertyValue("Efixed", "1.845"));
     TS_ASSERT_THROWS_NOTHING(conv.execute());
diff --git a/Framework/Algorithms/test/ConvertUnitsTest.h b/Framework/Algorithms/test/ConvertUnitsTest.h
index 43d01300fdd..c3d4574daed 100644
--- a/Framework/Algorithms/test/ConvertUnitsTest.h
+++ b/Framework/Algorithms/test/ConvertUnitsTest.h
@@ -13,6 +13,7 @@
 #include "MantidAPI/AnalysisDataService.h"
 #include "MantidAPI/Axis.h"
 #include "MantidAPI/MatrixWorkspace.h"
+#include "MantidAPI/SpectrumInfo.h"
 #include "MantidAlgorithms/ConvertToDistribution.h"
 #include "MantidAlgorithms/ConvertUnits.h"
 #include "MantidDataHandling/LoadInstrument.h"
@@ -674,7 +675,11 @@ public:
             outputSpace));
     TS_ASSERT_EQUALS(output->getAxis(0)->unit()->unitID(), "MomentumTransfer");
     TS_ASSERT_EQUALS(Mantid::Kernel::DeltaEMode::Direct, output->getEMode());
-    TS_ASSERT(std::isnan(output->x(0)[0]));
+
+    // conversion fails due to error in two theta calculation and leaves
+    // spectrum masked and zeroed
+    TS_ASSERT_EQUALS(output->y(0)[0], 0);
+    TS_ASSERT(output->spectrumInfo().isMasked(0));
   }
 
   void setup_Event() {
@@ -762,6 +767,13 @@ public:
     ws->getAxis(0)->setUnit("TOF");
     ws->sortAll(sortType, nullptr);
 
+    // 0th detector unfortunately has difc=0 which doesn't support conversion to
+    // d spacing so give it a more helpful difc value
+    auto instrument = ws->getInstrument();
+    auto det = instrument->getDetector(100);
+    auto &paramMap = ws->instrumentParameters();
+    paramMap.addDouble(det->getComponentID(), "DIFC", 1000);
+
     if (sortType == TOF_SORT) {
       // Only threadsafe if all the event lists are sorted
       TS_ASSERT(ws->threadSafe());
diff --git a/Framework/Algorithms/test/CreateDetectorTableTest.h b/Framework/Algorithms/test/CreateDetectorTableTest.h
index 4a297166979..66cf1963246 100644
--- a/Framework/Algorithms/test/CreateDetectorTableTest.h
+++ b/Framework/Algorithms/test/CreateDetectorTableTest.h
@@ -82,7 +82,7 @@ public:
     }
 
     // Check the results
-    TS_ASSERT_EQUALS(ws->columnCount(), 7);
+    TS_ASSERT_EQUALS(ws->columnCount(), 11);
     TS_ASSERT_EQUALS(ws->rowCount(), 2);
 
     // Remove workspace from the data service.
@@ -117,7 +117,7 @@ public:
     }
 
     // Check the results
-    TS_ASSERT_EQUALS(ws->columnCount(), 9);
+    TS_ASSERT_EQUALS(ws->columnCount(), 13);
     TS_ASSERT_EQUALS(ws->rowCount(), 1);
 
     // Remove workspace from the data service.
diff --git a/Framework/Algorithms/test/PDCalibrationTest.h b/Framework/Algorithms/test/PDCalibrationTest.h
index 3fdd6915205..6ca3bad06bd 100644
--- a/Framework/Algorithms/test/PDCalibrationTest.h
+++ b/Framework/Algorithms/test/PDCalibrationTest.h
@@ -21,7 +21,7 @@
 #include "MantidDataHandling/MoveInstrumentComponent.h"
 #include "MantidDataHandling/RotateInstrumentComponent.h"
 #include "MantidDataObjects/TableColumn.h"
-#include "MantidKernel/Diffraction.h"
+#include "MantidKernel/Unit.h"
 
 using Mantid::Algorithms::ConvertToMatrixWorkspace;
 using Mantid::Algorithms::CreateSampleWorkspace;
@@ -161,10 +161,13 @@ public:
 
   void test_exec_difc() {
     // setup the peak postions based on transformation from detID=155
-    std::vector<double> dValues(PEAK_TOFS.size());
-    std::transform(
-        PEAK_TOFS.begin(), PEAK_TOFS.end(), dValues.begin(),
-        Mantid::Kernel::Diffraction::getTofToDConversionFunc(DIFC_155, 0., 0.));
+    using Mantid::Kernel::UnitParams;
+    std::vector<double> dValues(PEAK_TOFS);
+    Mantid::Kernel::Units::dSpacing dSpacingUnit;
+    std::vector<double> unusedy;
+    dSpacingUnit.fromTOF(
+        dValues, unusedy, -1., 0,
+        Mantid::Kernel::UnitParametersMap{{UnitParams::difc, DIFC_155}});
 
     const std::string prefix{"PDCalibration_difc"};
 
@@ -219,12 +222,16 @@ public:
   }
 
   void test_exec_difc_tzero() {
+    using Mantid::Kernel::UnitParams;
     // setup the peak postions based on transformation from detID=155
     const double TZERO = 20.;
-    std::vector<double> dValues(PEAK_TOFS.size());
-    std::transform(PEAK_TOFS.begin(), PEAK_TOFS.end(), dValues.begin(),
-                   Mantid::Kernel::Diffraction::getTofToDConversionFunc(
-                       DIFC_155, 0., TZERO));
+    std::vector<double> dValues(PEAK_TOFS);
+    Mantid::Kernel::Units::dSpacing dSpacingUnit;
+    std::vector<double> unusedy;
+    dSpacingUnit.fromTOF(
+        dValues, unusedy, -1., 0,
+        Mantid::Kernel::UnitParametersMap{{UnitParams::difc, DIFC_155},
+                                          {UnitParams::tzero, TZERO}});
 
     const std::string prefix{"PDCalibration_difc_tzero"};
 
@@ -282,13 +289,17 @@ public:
   }
 
   void test_exec_difc_tzero_difa() {
+    using Mantid::Kernel::UnitParams;
     // setup the peak postions based on transformation from detID=155
     // allow refining DIFA, but don't set the transformation to require it
     const double TZERO = 20.;
-    std::vector<double> dValues(PEAK_TOFS.size());
-    std::transform(PEAK_TOFS.begin(), PEAK_TOFS.end(), dValues.begin(),
-                   Mantid::Kernel::Diffraction::getTofToDConversionFunc(
-                       DIFC_155, 0., TZERO));
+    std::vector<double> dValues(PEAK_TOFS);
+    Mantid::Kernel::Units::dSpacing dSpacingUnit;
+    std::vector<double> unusedy;
+    dSpacingUnit.fromTOF(
+        dValues, unusedy, -1., 0,
+        Mantid::Kernel::UnitParametersMap{{UnitParams::difc, DIFC_155},
+                                          {UnitParams::tzero, TZERO}});
 
     const std::string prefix{"PDCalibration_difc_tzero_difa"};
 
@@ -348,6 +359,7 @@ public:
   // Crop workspace so that final peak is evaluated over a range that includes
   // the last bin (stop regression out of range bug for histo workspaces)
   void test_exec_difc_histo() {
+    using Mantid::Kernel::UnitParams;
     // convert to histo
     ConvertToMatrixWorkspace convMatWS;
     convMatWS.initialize();
@@ -365,10 +377,13 @@ public:
     cropWS.execute();
 
     // setup the peak postions based on transformation from detID=155
-    std::vector<double> dValues(PEAK_TOFS.size());
-    std::transform(
-        PEAK_TOFS.begin(), PEAK_TOFS.end(), dValues.begin(),
-        Mantid::Kernel::Diffraction::getTofToDConversionFunc(DIFC_155, 0., 0.));
+    std::vector<double> dValues(PEAK_TOFS);
+
+    Mantid::Kernel::Units::dSpacing dSpacingUnit;
+    std::vector<double> unusedy;
+    dSpacingUnit.fromTOF(
+        dValues, unusedy, -1., 0,
+        Mantid::Kernel::UnitParametersMap{{UnitParams::difc, DIFC_155}});
 
     const std::string prefix{"PDCalibration_difc"};
 
@@ -414,13 +429,16 @@ public:
   }
 
   void test_exec_fit_diff_constants_with_chisq() {
+    using Mantid::Kernel::UnitParams;
     // setup the peak postions based on transformation from detID=155
     // allow refining DIFA, but don't set the transformation to require it
     // setup the peak postions based on transformation from detID=155
-    std::vector<double> dValues(PEAK_TOFS.size());
-    std::transform(
-        PEAK_TOFS.begin(), PEAK_TOFS.end(), dValues.begin(),
-        Mantid::Kernel::Diffraction::getTofToDConversionFunc(DIFC_155, 0., 0.));
+    std::vector<double> dValues(PEAK_TOFS);
+    Mantid::Kernel::Units::dSpacing dSpacingUnit;
+    std::vector<double> unusedy;
+    dSpacingUnit.fromTOF(
+        dValues, unusedy, -1., 0,
+        Mantid::Kernel::UnitParametersMap{{UnitParams::difc, DIFC_155}});
 
     const std::string prefix{"PDCalibration_difc"};
 
@@ -468,6 +486,7 @@ public:
   }
 
   void test_exec_grouped_detectors() {
+    using Mantid::Kernel::UnitParams;
     // group detectors
     GroupDetectors2 groupDet;
     groupDet.initialize();
@@ -477,10 +496,12 @@ public:
     groupDet.execute();
 
     // setup the peak postions based on transformation from detID=155
-    std::vector<double> dValues(PEAK_TOFS.size());
-    std::transform(
-        PEAK_TOFS.begin(), PEAK_TOFS.end(), dValues.begin(),
-        Mantid::Kernel::Diffraction::getTofToDConversionFunc(DIFC_155, 0., 0.));
+    std::vector<double> dValues(PEAK_TOFS);
+    Mantid::Kernel::Units::dSpacing dSpacingUnit;
+    std::vector<double> unusedy;
+    dSpacingUnit.fromTOF(
+        dValues, unusedy, -1., 0,
+        Mantid::Kernel::UnitParametersMap{{UnitParams::difc, DIFC_155}});
 
     const std::string prefix{"PDCalibration_difc"};
 
@@ -532,11 +553,14 @@ public:
   PDCalibrationTestPerformance() { FrameworkManager::Instance(); }
 
   void setUp() override {
+    using Mantid::Kernel::UnitParams;
     // setup the peak postions based on transformation from detID=155
     std::vector<double> dValues(PEAK_TOFS.size());
-    std::transform(
-        PEAK_TOFS.begin(), PEAK_TOFS.end(), dValues.begin(),
-        Mantid::Kernel::Diffraction::getTofToDConversionFunc(DIFC_155, 0., 0.));
+    Mantid::Kernel::Units::dSpacing dSpacingUnit;
+    std::vector<double> unusedy;
+    dSpacingUnit.fromTOF(
+        dValues, unusedy, -1., 0,
+        Mantid::Kernel::UnitParametersMap{{UnitParams::difc, DIFC_155}});
     createSampleWS();
     pdc.initialize();
     pdc.setProperty("InputWorkspace", "PDCalibrationTest_WS");
diff --git a/Framework/Algorithms/test/RemoveBackgroundTest.h b/Framework/Algorithms/test/RemoveBackgroundTest.h
index a1f956fb63d..de315e85e9b 100644
--- a/Framework/Algorithms/test/RemoveBackgroundTest.h
+++ b/Framework/Algorithms/test/RemoveBackgroundTest.h
@@ -95,13 +95,14 @@ public:
         std::vector<double>(1, 10.));
     TSM_ASSERT_THROWS(
         "Should throw if background workspace is not in TOF units",
-        bgRemoval.initialize(bkgWS, SourceWS, 0),
+        bgRemoval.initialize(bkgWS, SourceWS, Kernel::DeltaEMode::Elastic),
         const std::invalid_argument &);
 
     bkgWS = WorkspaceCreationHelper::create2DWorkspaceWithFullInstrument(2, 15);
-    TSM_ASSERT_THROWS("Should throw if background is not 1 or equal to source",
-                      bgRemoval.initialize(bkgWS, SourceWS, 0),
-                      const std::invalid_argument &);
+    TSM_ASSERT_THROWS(
+        "Should throw if background is not 1 or equal to source",
+        bgRemoval.initialize(bkgWS, SourceWS, Kernel::DeltaEMode::Elastic),
+        const std::invalid_argument &);
   }
 
   void testBackgroundHelper() {
@@ -110,7 +111,7 @@ public:
 
     API::AnalysisDataService::Instance().addOrReplace("TestWS", clone);
 
-    int emode = static_cast<int>(Kernel::DeltaEMode().fromString("Direct"));
+    auto emode = Kernel::DeltaEMode().fromString("Direct");
     bgRemoval.initialize(BgWS, SourceWS, emode);
 
     auto &dataX = clone->mutableX(0);
diff --git a/Framework/Algorithms/test/RemoveSpectraTest.h b/Framework/Algorithms/test/RemoveSpectraTest.h
index dd158032d81..33ddd35a7e8 100644
--- a/Framework/Algorithms/test/RemoveSpectraTest.h
+++ b/Framework/Algorithms/test/RemoveSpectraTest.h
@@ -11,6 +11,7 @@
 #include "MantidAPI/AlgorithmManager.h"
 #include "MantidAPI/AnalysisDataService.h"
 #include "MantidAPI/MatrixWorkspace.h"
+#include "MantidAPI/SpectrumInfo.h"
 #include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAlgorithms/RemoveSpectra.h"
 #include "MantidHistogramData/LinearGenerator.h"
@@ -89,6 +90,7 @@ public:
         AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(wsName);
     TS_ASSERT_EQUALS(inputWS->x(94).front(), 19900.0);
     TS_ASSERT_EQUALS(inputWS->x(144).front(), 19900.0);
+    TS_ASSERT(!inputWS->spectrumInfo().hasDetectors(144));
 
     RemoveSpectra alg;
     alg.initialize();
@@ -102,13 +104,13 @@ public:
     MatrixWorkspace_sptr outputWS =
         AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(
             ouputWsName);
-    // Removed specs are workspace indices 140 and 144/specNum 141 and 145
+    // Removed specs are workspace indices 94 and 144/specNum 95 and 145
     TS_ASSERT_EQUALS(outputWS->getNumberHistograms(), 147);
     TS_ASSERT_DELTA(outputWS->x(93).front(), 0.41157, 0.0001);  // was 93
     TS_ASSERT_DELTA(outputWS->x(94).front(), 0.05484, 0.0001);  // was 95
     TS_ASSERT_DELTA(outputWS->x(95).front(), -0.15111, 0.0001); // was 96
-    TS_ASSERT_DIFFERS(outputWS->x(143).front(),
-                      19900.0); // Would be 144 if 94 wasn't also removed
+    TS_ASSERT(outputWS->spectrumInfo().hasDetectors(
+        143)); // Would be 144 if 144 wasn't also removed
   }
 
 private:
diff --git a/Framework/Crystal/src/AnvredCorrection.cpp b/Framework/Crystal/src/AnvredCorrection.cpp
index 614c2ca3904..f2b7cb13e80 100644
--- a/Framework/Crystal/src/AnvredCorrection.cpp
+++ b/Framework/Crystal/src/AnvredCorrection.cpp
@@ -179,12 +179,14 @@ void AnvredCorrection::exec() {
     if (!spectrumInfo.hasDetectors(i) || spectrumInfo.isMonitor(i))
       continue;
 
-    // This is the scattered beam direction
     Instrument_const_sptr inst = m_inputWS->getInstrument();
-    double L2 = spectrumInfo.l2(i);
-    // Two-theta = polar angle = scattering angle = between +Z vector and the
-    // scattered beam
-    double scattering = spectrumInfo.twoTheta(i);
+    UnitParametersMap pmap{};
+    Mantid::Kernel::Units::Wavelength wl;
+    Mantid::Kernel::Units::TOF tof;
+    spectrumInfo.getDetectorValues(tof, wl, Kernel::DeltaEMode::Elastic, false,
+                                   i, pmap);
+    double L2 = pmap.at(UnitParams::l2);
+    double scattering = pmap.at(UnitParams::twoTheta);
 
     double depth = 0.2;
 
@@ -196,7 +198,6 @@ void AnvredCorrection::exec() {
     if (m_useScaleFactors)
       scale_init(det, inst, L2, depth, pathlength, bankName);
 
-    Mantid::Kernel::Units::Wavelength wl;
     auto points = m_inputWS->points(i);
 
     // share bin boundaries
@@ -213,10 +214,9 @@ void AnvredCorrection::exec() {
     // Loop through the bins in the current spectrum
     for (int64_t j = 0; j < specSize; j++) {
 
-      double lambda =
-          (unitStr == "TOF")
-              ? wl.convertSingleFromTOF(points[j], L1, L2, scattering, 0, 0, 0)
-              : points[j];
+      double lambda = (unitStr == "TOF")
+                          ? wl.convertSingleFromTOF(points[j], L1, 0, pmap)
+                          : points[j];
 
       if (m_returnTransmissionOnly) {
         Y[j] = 1.0 / this->getEventWeight(lambda, scattering);
@@ -280,18 +280,18 @@ void AnvredCorrection::execEvent() {
     if (!spectrumInfo.hasDetectors(i) || spectrumInfo.isMonitor(i))
       continue;
 
-    // This is the scattered beam direction
-    double L2 = spectrumInfo.l2(i);
-    // Two-theta = polar angle = scattering angle = between +Z vector and the
-    // scattered beam
-    double scattering = spectrumInfo.twoTheta(i);
+    UnitParametersMap pmap{};
+    Mantid::Kernel::Units::Wavelength wl;
+    Mantid::Kernel::Units::TOF tof;
+    spectrumInfo.getDetectorValues(tof, wl, Kernel::DeltaEMode::Elastic, false,
+                                   i, pmap);
+    double L2 = pmap.at(UnitParams::l2);
+    double scattering = pmap.at(UnitParams::twoTheta);
 
     EventList el = eventW->getSpectrum(i);
     el.switchTo(WEIGHTED_NOTIME);
     std::vector<WeightedEventNoTime> events = el.getWeightedEventsNoTime();
 
-    Mantid::Kernel::Units::Wavelength wl;
-
     double depth = 0.2;
     double pathlength = 0.0;
     std::string bankName;
@@ -306,7 +306,7 @@ void AnvredCorrection::execEvent() {
       double lambda = ev.tof();
 
       if ("TOF" == unitStr)
-        lambda = wl.convertSingleFromTOF(lambda, L1, L2, scattering, 0, 0, 0);
+        lambda = wl.convertSingleFromTOF(lambda, L1, 0, pmap);
 
       double value = this->getEventWeight(lambda, scattering);
 
diff --git a/Framework/Crystal/src/CentroidPeaks.cpp b/Framework/Crystal/src/CentroidPeaks.cpp
index 0625243a671..85d239310be 100644
--- a/Framework/Crystal/src/CentroidPeaks.cpp
+++ b/Framework/Crystal/src/CentroidPeaks.cpp
@@ -168,7 +168,8 @@ void CentroidPeaks::integrate() {
       double scattering = peak.getScattering();
       double L1 = peak.getL1();
       double L2 = peak.getL2();
-      wl.fromTOF(timeflight, timeflight, L1, L2, scattering, 0, 0, 0);
+      wl.fromTOF(timeflight, timeflight, L1, 0,
+                 {{UnitParams::l2, L2}, {UnitParams::twoTheta, scattering}});
       const double lambda = timeflight[0];
       timeflight.clear();
 
@@ -289,7 +290,8 @@ void CentroidPeaks::integrateEvent() {
       double scattering = peak.getScattering();
       double L1 = peak.getL1();
       double L2 = peak.getL2();
-      wl.fromTOF(timeflight, timeflight, L1, L2, scattering, 0, 0, 0);
+      wl.fromTOF(timeflight, timeflight, L1, 0,
+                 {{UnitParams::l2, L2}, {UnitParams::twoTheta, scattering}});
       const double lambda = timeflight[0];
       timeflight.clear();
 
diff --git a/Framework/Crystal/src/FindSXPeaksHelper.cpp b/Framework/Crystal/src/FindSXPeaksHelper.cpp
index c36783614c4..729f5db9d39 100644
--- a/Framework/Crystal/src/FindSXPeaksHelper.cpp
+++ b/Framework/Crystal/src/FindSXPeaksHelper.cpp
@@ -94,9 +94,17 @@ SXPeak::SXPeak(double t, double phi, double intensity,
   m_detId = spectrumInfo.detector(m_wsIndex).getID();
   m_nPixels = 1;
 
+  Mantid::Kernel::Units::TOF tof;
   const auto unit = Mantid::Kernel::UnitFactory::Instance().create("dSpacing");
-  unit->initialize(l1, l2, m_twoTheta, 0, 0, 0);
-  m_dSpacing = unit->singleFromTOF(m_tof);
+  Kernel::UnitParametersMap pmap{};
+  spectrumInfo.getDetectorValues(tof, *unit, Kernel::DeltaEMode::Elastic, false,
+                                 m_wsIndex, pmap);
+  unit->initialize(l1, 0, pmap);
+  try {
+    m_dSpacing = unit->singleFromTOF(m_tof);
+  } catch (std::exception &) {
+    m_dSpacing = 0;
+  }
 
   const auto samplePos = spectrumInfo.samplePosition();
   const auto sourcePos = spectrumInfo.sourcePosition();
@@ -365,9 +373,12 @@ double PeakFindingStrategy::convertToTOF(const double xValue,
     return xValue;
   } else {
     const auto unit = UnitFactory::Instance().create("dSpacing");
+    Mantid::Kernel::Units::TOF tof;
+    Kernel::UnitParametersMap pmap{};
+    m_spectrumInfo.getDetectorValues(*unit, tof, Kernel::DeltaEMode::Elastic,
+                                     false, workspaceIndex, pmap);
     // we're using d-spacing, convert the point to TOF
-    unit->initialize(m_spectrumInfo.l1(), m_spectrumInfo.l2(workspaceIndex),
-                     m_spectrumInfo.twoTheta(workspaceIndex), 0, 0, 0);
+    unit->initialize(m_spectrumInfo.l1(), 0, pmap);
     return unit->singleToTOF(xValue);
   }
 }
diff --git a/Framework/Crystal/src/LoadIsawPeaks.cpp b/Framework/Crystal/src/LoadIsawPeaks.cpp
index 47bcd6780c3..75d27e0deb8 100644
--- a/Framework/Crystal/src/LoadIsawPeaks.cpp
+++ b/Framework/Crystal/src/LoadIsawPeaks.cpp
@@ -525,8 +525,9 @@ void LoadIsawPeaks::appendFile(const PeaksWorkspace_sptr &outWS,
       double tof = peak.getTOF();
       Kernel::Units::Wavelength wl;
 
-      wl.initialize(peak.getL1(), peak.getL2(), peak.getScattering(), 0,
-                    peak.getInitialEnergy(), 0.0);
+      wl.initialize(peak.getL1(), 0,
+                    {{UnitParams::l2, peak.getL2()},
+                     {UnitParams::twoTheta, peak.getScattering()}});
 
       peak.setWavelength(wl.singleFromTOF(tof));
       // Add the peak to workspace
diff --git a/Framework/Crystal/src/LoadIsawSpectrum.cpp b/Framework/Crystal/src/LoadIsawSpectrum.cpp
index 5c9b438d279..03720f3dc4f 100644
--- a/Framework/Crystal/src/LoadIsawSpectrum.cpp
+++ b/Framework/Crystal/src/LoadIsawSpectrum.cpp
@@ -160,7 +160,11 @@ void LoadIsawSpectrum::exec() {
 
     Mantid::Kernel::Unit_sptr unit =
         UnitFactory::Instance().create("Wavelength");
-    unit->toTOF(xdata, ydata, l1, l2, theta2, 0, 0.0, 0.0);
+    unit->toTOF(xdata, ydata, l1, 0,
+                {
+                    {UnitParams::l2, l2},
+                    {UnitParams::twoTheta, theta2},
+                });
     double one = xdata[0];
     double spect1 = spectrumCalc(one, iSpec, time, spectra, i);
 
diff --git a/Framework/Crystal/src/NormaliseVanadium.cpp b/Framework/Crystal/src/NormaliseVanadium.cpp
index 77871cba04c..97ea0dcbbdb 100644
--- a/Framework/Crystal/src/NormaliseVanadium.cpp
+++ b/Framework/Crystal/src/NormaliseVanadium.cpp
@@ -86,17 +86,15 @@ void NormaliseVanadium::exec() {
     if (!spectrumInfo.hasDetectors(i))
       continue;
 
-    // This is the scattered beam direction
-    double L2 = spectrumInfo.l2(i);
-    // Two-theta = polar angle = scattering angle = between +Z vector and the
-    // scattered beam
-    double scattering = spectrumInfo.twoTheta(i);
-
     Mantid::Kernel::Units::Wavelength wl;
+    Mantid::Kernel::Units::TOF tof;
+    UnitParametersMap pmap{};
+    spectrumInfo.getDetectorValues(tof, wl, Kernel::DeltaEMode::Elastic, false,
+                                   i, pmap);
     auto timeflight = inSpec.points();
     if (unitStr == "TOF")
       wl.fromTOF(timeflight.mutableRawData(), timeflight.mutableRawData(), L1,
-                 L2, scattering, 0, 0, 0);
+                 0, pmap);
 
     // Loop through the bins in the current spectrum
     double lambp = 0;
diff --git a/Framework/Crystal/src/PeakHKLErrors.cpp b/Framework/Crystal/src/PeakHKLErrors.cpp
index 374410e132f..c65b2a42db0 100644
--- a/Framework/Crystal/src/PeakHKLErrors.cpp
+++ b/Framework/Crystal/src/PeakHKLErrors.cpp
@@ -662,8 +662,10 @@ Peak PeakHKLErrors::createNewPeak(const DataObjects::Peak &peak_old,
 
   Wavelength wl;
 
-  wl.initialize(L0, peak.getL2(), peak.getScattering(), 0,
-                peak_old.getInitialEnergy(), 0.0);
+  wl.initialize(L0, 0,
+                {{UnitParams::l2, peak.getL2()},
+                 {UnitParams::twoTheta, peak.getScattering()},
+                 {UnitParams::efixed, peak_old.getInitialEnergy()}});
 
   peak.setWavelength(wl.singleFromTOF(T));
   peak.setIntensity(peak_old.getIntensity());
diff --git a/Framework/Crystal/src/SCDCalibratePanels.cpp b/Framework/Crystal/src/SCDCalibratePanels.cpp
index 9762c35f20a..a878f1a59d0 100644
--- a/Framework/Crystal/src/SCDCalibratePanels.cpp
+++ b/Framework/Crystal/src/SCDCalibratePanels.cpp
@@ -379,8 +379,9 @@ void SCDCalibratePanels::findT0(
 
     Units::Wavelength wl;
 
-    wl.initialize(peak.getL1(), peak.getL2(), peak.getScattering(), 0,
-                  peak.getInitialEnergy(), 0.0);
+    wl.initialize(peak.getL1(), 0,
+                  {{UnitParams::l2, peak.getL2()},
+                   {UnitParams::twoTheta, peak.getScattering()}});
     peak.setWavelength(wl.singleFromTOF(peak.getTOF() + mT0));
   }
 }
diff --git a/Framework/Crystal/src/SCDCalibratePanels2.cpp b/Framework/Crystal/src/SCDCalibratePanels2.cpp
index 71b39d29fa9..e54bd269471 100644
--- a/Framework/Crystal/src/SCDCalibratePanels2.cpp
+++ b/Framework/Crystal/src/SCDCalibratePanels2.cpp
@@ -685,8 +685,10 @@ void SCDCalibratePanels2::adjustT0(double dT0, IPeaksWorkspace_sptr &pws) {
   for (int i = 0; i < pws->getNumberPeaks(); ++i) {
     IPeak &pk = pws->getPeak(i);
     Units::Wavelength wl;
-    wl.initialize(pk.getL1(), pk.getL2(), pk.getScattering(), 0,
-                  pk.getInitialEnergy(), 0.0);
+    wl.initialize(pk.getL1(), 0,
+                  {{UnitParams::l2, pk.getL2()},
+                   {UnitParams::twoTheta, pk.getScattering()},
+                   {UnitParams::efixed, pk.getInitialEnergy()}});
     pk.setWavelength(wl.singleFromTOF(pk.getTOF() + dT0));
   }
 }
diff --git a/Framework/Crystal/src/SCDCalibratePanels2ObjFunc.cpp b/Framework/Crystal/src/SCDCalibratePanels2ObjFunc.cpp
index 64c88ab9e87..cba4f70f11e 100644
--- a/Framework/Crystal/src/SCDCalibratePanels2ObjFunc.cpp
+++ b/Framework/Crystal/src/SCDCalibratePanels2ObjFunc.cpp
@@ -137,8 +137,10 @@ void SCDCalibratePanels2ObjFunc::function1D(double *out, const double *xValues,
     pk.setDetectorID(pk.getDetectorID());
     // calculate&set wavelength based on new instrument
     Units::Wavelength wl;
-    wl.initialize(pk.getL1(), pk.getL2(), pk.getScattering(), 0,
-                  pk.getInitialEnergy(), 0.0);
+    wl.initialize(pk.getL1(), 0,
+                  {{UnitParams::l2, pk.getL2()},
+                   {UnitParams::twoTheta, pk.getScattering()},
+                   {UnitParams::efixed, pk.getInitialEnergy()}});
     pk.setWavelength(wl.singleFromTOF(tof));
 
     V3D qv = pk.getQSampleFrame();
diff --git a/Framework/Crystal/src/SCDPanelErrors.cpp b/Framework/Crystal/src/SCDPanelErrors.cpp
index 6dbd04977f1..5a303969a0d 100644
--- a/Framework/Crystal/src/SCDPanelErrors.cpp
+++ b/Framework/Crystal/src/SCDPanelErrors.cpp
@@ -213,8 +213,9 @@ void SCDPanelErrors::eval(double xshift, double yshift, double zshift,
                               hkl, peak.getGoniometerMatrix());
       Units::Wavelength wl;
 
-      wl.initialize(peak2.getL1(), peak2.getL2(), peak2.getScattering(), 0,
-                    peak2.getInitialEnergy(), 0.0);
+      wl.initialize(peak2.getL1(), 0,
+                    {{UnitParams::l2, peak2.getL2()},
+                     {UnitParams::twoTheta, peak2.getScattering()}});
       peak2.setWavelength(wl.singleFromTOF(peak.getTOF() + tShift));
       V3D Q3 = peak2.getQSampleFrame();
       out[i * 3] = Q3[0] - Q2[0];
diff --git a/Framework/Crystal/src/SaveHKL.cpp b/Framework/Crystal/src/SaveHKL.cpp
index 179739523c4..0e7fd62c1d8 100644
--- a/Framework/Crystal/src/SaveHKL.cpp
+++ b/Framework/Crystal/src/SaveHKL.cpp
@@ -435,7 +435,8 @@ void SaveHKL::exec() {
           double l2 = p.getL2();
           Mantid::Kernel::Unit_sptr unit =
               UnitFactory::Instance().create("Wavelength");
-          unit->toTOF(xdata, ydata, l1, l2, theta2, 0, 0.0, 0.0);
+          unit->toTOF(xdata, ydata, l1, 0,
+                      {{UnitParams::l2, l2}, {UnitParams::twoTheta, theta2}});
           double one = xdata[0];
           double spect1 = spectrumCalc(one, iSpec, time, spectra, bank);
           relSigSpect = std::sqrt((1.0 / spect) + (1.0 / spect1));
diff --git a/Framework/Crystal/test/FindSXPeaksHelperTest.h b/Framework/Crystal/test/FindSXPeaksHelperTest.h
index 95070d6b928..eb1144aca50 100644
--- a/Framework/Crystal/test/FindSXPeaksHelperTest.h
+++ b/Framework/Crystal/test/FindSXPeaksHelperTest.h
@@ -140,13 +140,13 @@ public:
     auto backgroundStrategy = std::make_unique<AbsoluteBackgroundStrategy>(3.);
     auto workspace =
         WorkspaceCreationHelper::create2DWorkspaceWithFullInstrument(
-            1 /*nhist*/, 15 /*nbins*/);
-    auto &mutableY = workspace->mutableY(0);
+            2 /*nhist*/, 15 /*nbins*/);
+    const int workspaceIndex = 1;
+    auto &mutableY = workspace->mutableY(workspaceIndex);
     doAddDoublePeakToData(mutableY);
 
-    const int workspaceIndex = 0;
-    const auto &x = workspace->x(0);
-    const auto &y = workspace->y(0);
+    const auto &x = workspace->x(workspaceIndex);
+    const auto &y = workspace->y(workspaceIndex);
     const auto &spectrumInfo = workspace->spectrumInfo();
 
     // WHEN
@@ -398,13 +398,13 @@ private:
     // GIVEN
     auto workspace =
         WorkspaceCreationHelper::create2DWorkspaceWithFullInstrument(
-            1 /*nhist*/, 15 /*nbins*/);
-    auto &mutableY = workspace->mutableY(0);
+            2 /*nhist*/, 15 /*nbins*/);
+    const int workspaceIndex = 1;
+    auto &mutableY = workspace->mutableY(workspaceIndex);
     doAddDoublePeakToData(mutableY);
 
-    const int workspaceIndex = 0;
-    const auto &x = workspace->x(0);
-    const auto &y = workspace->y(0);
+    const auto &x = workspace->x(workspaceIndex);
+    const auto &y = workspace->y(workspaceIndex);
     const auto &spectrumInfo = workspace->spectrumInfo();
 
     // WHEN
diff --git a/Framework/Crystal/test/IntegratePeakTimeSlicesTest.h b/Framework/Crystal/test/IntegratePeakTimeSlicesTest.h
index 239cd875bb4..48f83a77503 100644
--- a/Framework/Crystal/test/IntegratePeakTimeSlicesTest.h
+++ b/Framework/Crystal/test/IntegratePeakTimeSlicesTest.h
@@ -115,7 +115,9 @@ public:
     std::vector<double> x;
     x.emplace_back(PeakTime);
 
-    wl.fromTOF(x, x, L1, L2, ScatAng, 0, 0, 0);
+    wl.fromTOF(x, x, L1, 0,
+               {{Kernel::UnitParams::l2, L2},
+                {Kernel::UnitParams::twoTheta, ScatAng}});
     double wavelength = x[0];
 
     Peak peak(instP, pixelp->getID(), wavelength);
@@ -250,7 +252,9 @@ private:
     x.emplace_back(time);
     const auto ScatAng = detectorInfo.twoTheta(detInfoIndex) / 180 * M_PI;
 
-    Q.fromTOF(x, x, L1, L2, ScatAng, 0, 0, 0.0);
+    Q.fromTOF(x, x, L1, 0,
+              {{Kernel::UnitParams::l2, L2},
+               {Kernel::UnitParams::twoTheta, ScatAng}});
 
     return x[0] / 2 / M_PI;
   }
diff --git a/Framework/Crystal/test/SCDCalibratePanels2Test.h b/Framework/Crystal/test/SCDCalibratePanels2Test.h
index 3b8dbd3a246..f5516fcfe30 100644
--- a/Framework/Crystal/test/SCDCalibratePanels2Test.h
+++ b/Framework/Crystal/test/SCDCalibratePanels2Test.h
@@ -449,9 +449,10 @@ private:
     for (int i = 0; i < pws->getNumberPeaks(); ++i) {
       tof = pws->getPeak(i).getTOF();
       pws->getPeak(i).setInstrument(pws->getInstrument());
-      wl.initialize(pws->getPeak(i).getL1(), pws->getPeak(i).getL2(),
-                    pws->getPeak(i).getScattering(), 0,
-                    pws->getPeak(i).getInitialEnergy(), 0.0);
+      wl.initialize(pws->getPeak(i).getL1(), 0,
+                    {{UnitParams::l2, pws->getPeak(i).getL2()},
+                     {UnitParams::twoTheta, pws->getPeak(i).getScattering()},
+                     {UnitParams::efixed, pws->getPeak(i).getInitialEnergy()}});
       pws->getPeak(i).setDetectorID(pws->getPeak(i).getDetectorID());
       pws->getPeak(i).setWavelength(wl.singleFromTOF(tof));
     }
diff --git a/Framework/CurveFitting/src/Algorithms/PawleyFit.cpp b/Framework/CurveFitting/src/Algorithms/PawleyFit.cpp
index 9ba22ff2e15..e153fc4da3a 100644
--- a/Framework/CurveFitting/src/Algorithms/PawleyFit.cpp
+++ b/Framework/CurveFitting/src/Algorithms/PawleyFit.cpp
@@ -51,8 +51,7 @@ double PawleyFit::getTransformedCenter(double d, const Unit_sptr &unit) const {
     return d;
   }
 
-  return UnitConversion::run(*m_dUnit, *unit, d, 0, 0, 0, DeltaEMode::Elastic,
-                             0);
+  return UnitConversion::run(*m_dUnit, *unit, d, 0, DeltaEMode::Elastic);
 }
 
 /**
diff --git a/Framework/CurveFitting/src/Functions/PawleyFunction.cpp b/Framework/CurveFitting/src/Functions/PawleyFunction.cpp
index 3d47ea64dab..a842433a1d9 100644
--- a/Framework/CurveFitting/src/Functions/PawleyFunction.cpp
+++ b/Framework/CurveFitting/src/Functions/PawleyFunction.cpp
@@ -398,8 +398,7 @@ void PawleyFunction::setUnitCell(const std::string &unitCellString) {
 /// Transform d value to workspace unit
 double PawleyFunction::getTransformedCenter(double d) const {
   if ((m_dUnit && m_wsUnit) && m_dUnit != m_wsUnit) {
-    return UnitConversion::run(*m_dUnit, *m_wsUnit, d, 0, 0, 0,
-                               DeltaEMode::Elastic, 0);
+    return UnitConversion::run(*m_dUnit, *m_wsUnit, d, 0, DeltaEMode::Elastic);
   }
 
   return d;
diff --git a/Framework/DataHandling/CMakeLists.txt b/Framework/DataHandling/CMakeLists.txt
index b0ce8cf67d0..8987e354d46 100644
--- a/Framework/DataHandling/CMakeLists.txt
+++ b/Framework/DataHandling/CMakeLists.txt
@@ -1,5 +1,6 @@
 set(SRC_FILES
     src/AppendGeometryToSNSNexus.cpp
+    src/ApplyDiffCal.cpp
     src/BankPulseTimes.cpp
     src/CheckMantidVersion.cpp
     src/CompressEvents.cpp
@@ -212,6 +213,7 @@ set(SRC_FILES
 
 set(INC_FILES
     inc/MantidDataHandling/AppendGeometryToSNSNexus.h
+    inc/MantidDataHandling/ApplyDiffCal.h
     inc/MantidDataHandling/BankPulseTimes.h
     inc/MantidDataHandling/CheckMantidVersion.h
     inc/MantidDataHandling/CompressEvents.h
@@ -427,6 +429,7 @@ set(INC_FILES
 
 set(TEST_FILES
     AppendGeometryToSNSNexusTest.h
+    ApplyDiffCalTest.h
     CheckMantidVersionTest.h
     CompressEventsTest.h
     CreateChunkingFromInstrumentTest.h
diff --git a/Framework/DataHandling/inc/MantidDataHandling/ApplyDiffCal.h b/Framework/DataHandling/inc/MantidDataHandling/ApplyDiffCal.h
new file mode 100644
index 00000000000..c5953cd4c0b
--- /dev/null
+++ b/Framework/DataHandling/inc/MantidDataHandling/ApplyDiffCal.h
@@ -0,0 +1,44 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2020 ISIS Rutherford Appleton Laboratory UKRI,
+//   NScD Oak Ridge National Laboratory, European Spallation Source,
+//   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
+// SPDX - License - Identifier: GPL - 3.0 +
+#pragma once
+
+#include "MantidAPI/Algorithm.h"
+#include "MantidAPI/ITableWorkspace_fwd.h"
+#include "MantidAPI/Workspace_fwd.h"
+#include "MantidGeometry/Instrument.h"
+#include "MantidKernel/System.h"
+
+namespace Mantid {
+namespace DataHandling {
+
+/** ApplyDiffCal :
+ */
+class DLLExport ApplyDiffCal : public API::Algorithm {
+public:
+  const std::string name() const override;
+  int version() const override;
+  const std::vector<std::string> seeAlso() const override {
+    return {"AlignDetectors", "ConvertDiffCal", "ConvertUnits", "LoadDiffCal"};
+  }
+  const std::string category() const override;
+  const std::string summary() const override;
+  /// Cross-check properties with each other @see IAlgorithm::validateInputs
+  std::map<std::string, std::string> validateInputs() override;
+
+private:
+  void init() override;
+  void exec() override;
+
+  void loadCalFile(const Mantid::API::Workspace_sptr &inputWS,
+                   const std::string &filename);
+  void getCalibrationWS(const Mantid::API::Workspace_sptr &inputWS);
+
+  Mantid::API::ITableWorkspace_sptr m_calibrationWS;
+};
+
+} // namespace DataHandling
+} // namespace Mantid
diff --git a/Framework/DataHandling/inc/MantidDataHandling/LoadGSS.h b/Framework/DataHandling/inc/MantidDataHandling/LoadGSS.h
index cefa59b10e4..5d4d94eb890 100644
--- a/Framework/DataHandling/inc/MantidDataHandling/LoadGSS.h
+++ b/Framework/DataHandling/inc/MantidDataHandling/LoadGSS.h
@@ -68,7 +68,8 @@ private:
                                 const double &primaryflightpath,
                                 const std::vector<int> &detectorids,
                                 const std::vector<double> &totalflightpaths,
-                                const std::vector<double> &twothetas);
+                                const std::vector<double> &twothetas,
+                                const std::vector<double> &difcs);
 };
 } // namespace DataHandling
 } // namespace Mantid
diff --git a/Framework/DataHandling/src/ApplyDiffCal.cpp b/Framework/DataHandling/src/ApplyDiffCal.cpp
new file mode 100644
index 00000000000..a1986daa0aa
--- /dev/null
+++ b/Framework/DataHandling/src/ApplyDiffCal.cpp
@@ -0,0 +1,227 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2020 ISIS Rutherford Appleton Laboratory UKRI,
+//   NScD Oak Ridge National Laboratory, European Spallation Source,
+//   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
+// SPDX - License - Identifier: GPL - 3.0 +
+#include "MantidDataHandling/ApplyDiffCal.h"
+
+#include "MantidAPI/FileProperty.h"
+#include "MantidAPI/ITableWorkspace.h"
+#include "MantidAPI/Progress.h"
+#include "MantidAPI/Run.h"
+#include "MantidAPI/TableRow.h"
+#include "MantidAPI/Workspace.h"
+#include "MantidDataObjects/MaskWorkspace.h"
+#include "MantidDataObjects/OffsetsWorkspace.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidKernel/EnabledWhenProperty.h"
+
+using namespace Mantid::API;
+using namespace Mantid::DataObjects;
+
+namespace Mantid {
+namespace DataHandling {
+
+using Mantid::Kernel::Direction;
+using Mantid::Kernel::PropertyWithValue;
+
+namespace {
+namespace PropertyNames {
+const std::string CAL_FILE("Filename");
+const std::string GROUP_FILE("GroupFilename");
+const std::string MAKE_CAL("MakeCalWorkspace");
+const std::string MAKE_GRP("MakeGroupingWorkspace");
+const std::string MAKE_MSK("MakeMaskWorkspace");
+} // namespace PropertyNames
+} // namespace
+
+// Register the algorithm into the AlgorithmFactory
+DECLARE_ALGORITHM(ApplyDiffCal)
+
+/// Algorithms name for identification. @see Algorithm::name
+const std::string ApplyDiffCal::name() const { return "ApplyDiffCal"; }
+
+/// Algorithm's version for identification. @see Algorithm::version
+int ApplyDiffCal::version() const { return 1; }
+
+/// Algorithm's category for identification. @see Algorithm::category
+const std::string ApplyDiffCal::category() const {
+  return "DataHandling\\Instrument;Diffraction\\DataHandling";
+}
+
+/// Algorithm's summary for use in the GUI and help. @see Algorithm::summary
+const std::string ApplyDiffCal::summary() const {
+  return "Applies a calibration to a workspace for powder diffraction";
+}
+
+/** Initialize the algorithm's properties.
+ */
+void ApplyDiffCal::init() {
+  declareProperty(std::make_unique<WorkspaceProperty<API::Workspace>>(
+                      "InstrumentWorkspace", "", Direction::InOut),
+                  "Set the workspace whose instrument should be updated");
+  const std::vector<std::string> exts{".h5", ".hd5", ".hdf", ".cal"};
+  declareProperty(
+      std::make_unique<FileProperty>("CalibrationFile", "",
+                                     FileProperty::OptionalLoad, exts),
+      "Optional: The .cal file containing the position correction factors. "
+      "Either this, CalibrationWorkspace or OffsetsWorkspace needs to be "
+      "specified.");
+  declareProperty(
+      std::make_unique<WorkspaceProperty<ITableWorkspace>>(
+          "CalibrationWorkspace", "", Direction::Input, PropertyMode::Optional),
+      "Optional: Set the Diffraction Calibration workspace");
+  declareProperty(
+      std::make_unique<WorkspaceProperty<OffsetsWorkspace>>(
+          "OffsetsWorkspace", "", Direction::Input, PropertyMode::Optional),
+      "Optional: A OffsetsWorkspace containing the calibration offsets. Either "
+      "this, CalibrationWorkspace or CalibrationFile needs to be specified.");
+  declareProperty("ClearCalibration", false,
+                  "Remove any existing calibration from the workspace");
+  setPropertySettings(
+      "CalibrationFile",
+      std::make_unique<Kernel::EnabledWhenProperty>(
+          "ClearCalibration", Kernel::ePropertyCriterion::IS_EQUAL_TO, "0"));
+  setPropertySettings(
+      "CalibrationWorkspace",
+      std::make_unique<Kernel::EnabledWhenProperty>(
+          "ClearCalibration", Kernel::ePropertyCriterion::IS_EQUAL_TO, "0"));
+  setPropertySettings(
+      "OffsetsWorkspace",
+      std::make_unique<Kernel::EnabledWhenProperty>(
+          "ClearCalibration", Kernel::ePropertyCriterion::IS_EQUAL_TO, "0"));
+}
+
+std::map<std::string, std::string> ApplyDiffCal::validateInputs() {
+  std::map<std::string, std::string> result;
+
+  // Check workspace type has ExperimentInfo fields
+  using API::ExperimentInfo_sptr;
+  using API::Workspace_sptr;
+  Workspace_sptr inputWS = getProperty("InstrumentWorkspace");
+  if (!std::dynamic_pointer_cast<ExperimentInfo>(inputWS)) {
+    result["InstrumentWorkspace"] = "InputWorkspace type invalid. "
+                                    "Expected MatrixWorkspace, "
+                                    "PeaksWorkspace.";
+  }
+
+  int numWays = 0;
+
+  const std::string calFileName = getProperty("CalibrationFile");
+  if (!calFileName.empty())
+    numWays += 1;
+
+  ITableWorkspace_const_sptr calibrationWS =
+      getProperty("CalibrationWorkspace");
+  if (bool(calibrationWS))
+    numWays += 1;
+
+  OffsetsWorkspace_const_sptr offsetsWS = getProperty("OffsetsWorkspace");
+  if (bool(offsetsWS))
+    numWays += 1;
+
+  bool clearCalibration = getProperty("ClearCalibration");
+  std::string message;
+  if ((clearCalibration) && (numWays > 0)) {
+    message =
+        "You cannot supply a calibration input when clearing the calibration.";
+  }
+  if (!clearCalibration) {
+    if (numWays == 0) {
+      message = "You must specify only one of CalibrationFile, "
+                "CalibrationWorkspace, OffsetsWorkspace.";
+    }
+    if (numWays > 1) {
+      message = "You must specify one of CalibrationFile, "
+                "CalibrationWorkspace, OffsetsWorkspace.";
+    }
+  }
+
+  if (!message.empty()) {
+    result["CalibrationFile"] = message;
+    result["CalibrationWorkspace"] = message;
+  }
+
+  return result;
+}
+
+void ApplyDiffCal::loadCalFile(const Workspace_sptr &inputWS,
+                               const std::string &filename) {
+  IAlgorithm_sptr alg = createChildAlgorithm("LoadDiffCal");
+  alg->setProperty("InputWorkspace", inputWS);
+  alg->setPropertyValue("Filename", filename);
+  alg->setProperty<bool>("MakeCalWorkspace", true);
+  alg->setProperty<bool>("MakeGroupingWorkspace", false);
+  alg->setProperty<bool>("MakeMaskWorkspace", false);
+  alg->setPropertyValue("WorkspaceName", "temp");
+  alg->executeAsChildAlg();
+
+  m_calibrationWS = alg->getProperty("OutputCalWorkspace");
+}
+
+void ApplyDiffCal::getCalibrationWS(const Workspace_sptr &inputWS) {
+  m_calibrationWS = getProperty("CalibrationWorkspace");
+  if (m_calibrationWS)
+    return; // nothing more to do
+
+  OffsetsWorkspace_sptr offsetsWS = getProperty("OffsetsWorkspace");
+  if (offsetsWS) {
+    auto alg = createChildAlgorithm("ConvertDiffCal");
+    alg->setProperty("OffsetsWorkspace", offsetsWS);
+    alg->executeAsChildAlg();
+    m_calibrationWS = alg->getProperty("OutputWorkspace");
+    m_calibrationWS->setTitle(offsetsWS->getTitle());
+    return;
+  }
+
+  const std::string calFileName = getPropertyValue("CalibrationFile");
+  if (!calFileName.empty()) {
+    progress(0.0, "Reading calibration file");
+    loadCalFile(std::move(inputWS), calFileName);
+    return;
+  }
+
+  throw std::runtime_error("Failed to determine calibration information");
+}
+
+//----------------------------------------------------------------------------------------------
+/** Execute the algorithm.
+ */
+void ApplyDiffCal::exec() {
+
+  Workspace_sptr InstrumentWorkspace = getProperty("InstrumentWorkspace");
+  // validateInputs guarantees this will be an ExperimentInfo object
+  auto experimentInfo =
+      std::dynamic_pointer_cast<API::ExperimentInfo>(InstrumentWorkspace);
+  auto instrument = experimentInfo->getInstrument();
+  auto &paramMap = experimentInfo->instrumentParameters();
+  bool clearCalibration = getProperty("ClearCalibration");
+  if (clearCalibration) {
+    paramMap.clearParametersByName("DIFC");
+    paramMap.clearParametersByName("DIFA");
+    paramMap.clearParametersByName("TZERO");
+  } else {
+    this->getCalibrationWS(InstrumentWorkspace);
+
+    Column_const_sptr detIdColumn = m_calibrationWS->getColumn("detid");
+    Column_const_sptr difcColumn = m_calibrationWS->getColumn("difc");
+    Column_const_sptr difaColumn = m_calibrationWS->getColumn("difa");
+    Column_const_sptr tzeroColumn = m_calibrationWS->getColumn("tzero");
+
+    for (size_t i = 0; i < m_calibrationWS->rowCount(); ++i) {
+      auto detid = static_cast<detid_t>((*detIdColumn)[i]);
+      double difc = (*difcColumn)[i];
+      double difa = (*difaColumn)[i];
+      double tzero = (*tzeroColumn)[i];
+
+      auto det = instrument->getDetector(detid);
+      paramMap.addDouble(det->getComponentID(), "DIFC", difc);
+      paramMap.addDouble(det->getComponentID(), "DIFA", difa);
+      paramMap.addDouble(det->getComponentID(), "TZERO", tzero);
+    }
+  }
+}
+
+} // namespace DataHandling
+} // namespace Mantid
diff --git a/Framework/DataHandling/src/LoadDiffCal.cpp b/Framework/DataHandling/src/LoadDiffCal.cpp
index b28e4399d40..d10d84a0bc9 100644
--- a/Framework/DataHandling/src/LoadDiffCal.cpp
+++ b/Framework/DataHandling/src/LoadDiffCal.cpp
@@ -17,9 +17,9 @@
 #include "MantidDataObjects/MaskWorkspace.h"
 #include "MantidDataObjects/TableWorkspace.h"
 #include "MantidDataObjects/Workspace2D.h"
-#include "MantidKernel/Diffraction.h"
 #include "MantidKernel/Exception.h"
 #include "MantidKernel/OptionalBool.h"
+#include "MantidKernel/Unit.h"
 
 #include <H5Cpp.h>
 #include <cmath>
@@ -315,8 +315,9 @@ void LoadDiffCal::makeCalWorkspace(const std::vector<int32_t> &detids,
       newrow << offsets[i];
 
     // calculate tof range for information
+    Kernel::Units::dSpacing dspacingUnit;
     const double tofMinRow =
-        Kernel::Diffraction::calcTofMin(difc[i], difa[i], tzero[i], tofMin);
+        dspacingUnit.calcTofMin(difc[i], difa[i], tzero[i], tofMin);
     std::stringstream msg;
     if (tofMinRow != tofMin) {
       msg << "TofMin shifted from " << tofMin << " to " << tofMinRow << " ";
@@ -324,7 +325,7 @@ void LoadDiffCal::makeCalWorkspace(const std::vector<int32_t> &detids,
     newrow << tofMinRow;
     if (useTofMax) {
       const double tofMaxRow =
-          Kernel::Diffraction::calcTofMax(difc[i], difa[i], tzero[i], tofMax);
+          dspacingUnit.calcTofMax(difc[i], difa[i], tzero[i], tofMax);
       newrow << tofMaxRow;
 
       if (tofMaxRow != tofMax) {
diff --git a/Framework/DataHandling/src/LoadGSS.cpp b/Framework/DataHandling/src/LoadGSS.cpp
index 3c3ab417877..b4c8a2b97e9 100644
--- a/Framework/DataHandling/src/LoadGSS.cpp
+++ b/Framework/DataHandling/src/LoadGSS.cpp
@@ -458,7 +458,7 @@ API::MatrixWorkspace_sptr LoadGSS::loadGSASFile(const std::string &filename,
 
   // build instrument geometry
   createInstrumentGeometry(outputWorkspace, instrumentname, primaryflightpath,
-                           detectorIDs, totalflightpaths, twothetas);
+                           detectorIDs, totalflightpaths, twothetas, difcs);
 
   return outputWorkspace;
 }
@@ -491,7 +491,7 @@ void LoadGSS::createInstrumentGeometry(
     const MatrixWorkspace_sptr &workspace, const std::string &instrumentname,
     const double &primaryflightpath, const std::vector<int> &detectorids,
     const std::vector<double> &totalflightpaths,
-    const std::vector<double> &twothetas) {
+    const std::vector<double> &twothetas, const std::vector<double> &difcs) {
   // Check Input
   if (detectorids.size() != totalflightpaths.size() ||
       totalflightpaths.size() != twothetas.size()) {
@@ -560,7 +560,13 @@ void LoadGSS::createInstrumentGeometry(
 
   } // ENDFOR (i: spectrum)
   workspace->setInstrument(instrument);
-}
 
+  auto &paramMap = workspace->instrumentParameters();
+  for (size_t i = 0; i < workspace->getNumberHistograms(); i++) {
+    auto detector = workspace->getDetector(i);
+    paramMap.addDouble(detector->getComponentID(), "DIFC", difcs[i]);
+  }
+}
 } // namespace DataHandling
+
 } // namespace Mantid
diff --git a/Framework/DataHandling/src/SaveGDA.cpp b/Framework/DataHandling/src/SaveGDA.cpp
index f7230ebd141..85efe734015 100644
--- a/Framework/DataHandling/src/SaveGDA.cpp
+++ b/Framework/DataHandling/src/SaveGDA.cpp
@@ -162,7 +162,7 @@ void SaveGDA::exec() {
     const auto ws = inputWS->getItem(i);
     const auto matrixWS = std::dynamic_pointer_cast<MatrixWorkspace>(ws);
 
-    const auto &d = matrixWS->x(0);
+    auto x = matrixWS->dataX(0);
     const size_t bankIndex(groupingScheme[i] - 1);
     if (bankIndex >= calibParams.size()) {
       throw Kernel::Exception::IndexError(bankIndex, calibParams.size(),
@@ -173,14 +173,15 @@ void SaveGDA::exec() {
     // For historic reasons, TOF is scaled by 32 in MAUD
     const static double tofScale = 32;
     std::vector<double> tofScaled;
-    tofScaled.reserve(d.size());
-    std::transform(d.begin(), d.end(), std::back_inserter(tofScaled),
-                   [&bankCalibParams](const double dVal) {
-                     return (dVal * bankCalibParams.difc +
-                             dVal * dVal * bankCalibParams.difa +
-                             bankCalibParams.tzero) *
-                            tofScale;
-                   });
+    tofScaled.reserve(x.size());
+    Kernel::Units::dSpacing dSpacingUnit;
+    std::vector<double> yunused;
+    dSpacingUnit.toTOF(x, yunused, 0., Kernel::DeltaEMode::Elastic,
+                       {{Kernel::UnitParams::difa, bankCalibParams.difa},
+                        {Kernel::UnitParams::difc, bankCalibParams.difc},
+                        {Kernel::UnitParams::tzero, bankCalibParams.tzero}});
+    std::transform(x.begin(), x.end(), std::back_inserter(tofScaled),
+                   [](const double tofVal) { return tofVal * tofScale; });
     const auto averageDeltaTByT = computeAverageDeltaTByT(tofScaled);
 
     const auto &intensity = matrixWS->y(0);
diff --git a/Framework/DataHandling/src/SaveGSS.cpp b/Framework/DataHandling/src/SaveGSS.cpp
index 05372a28b13..82916261426 100644
--- a/Framework/DataHandling/src/SaveGSS.cpp
+++ b/Framework/DataHandling/src/SaveGSS.cpp
@@ -361,11 +361,10 @@ void SaveGSS::generateBankHeader(std::stringstream &out,
     const auto l1 = spectrumInfo.l1();
     const auto l2 = spectrumInfo.l2(specIndex);
     const auto twoTheta = spectrumInfo.twoTheta(specIndex);
-    const auto difc = (2.0 * PhysicalConstants::NeutronMass *
-                       sin(twoTheta * 0.5) * (l1 + l2)) /
-                      (PhysicalConstants::h * 1.e4);
+    auto diffConstants = spectrumInfo.diffractometerConstants(specIndex);
     out << "# Total flight path " << (l1 + l2) << "m, tth "
-        << (twoTheta * 180. / M_PI) << "deg, DIFC " << difc << "\n";
+        << (twoTheta * 180. / M_PI) << "deg, DIFC "
+        << diffConstants[Kernel::UnitParams::difc] << "\n";
   }
   out << "# Data for spectrum :" << specIndex << "\n";
 }
diff --git a/Framework/DataHandling/test/ApplyDiffCalTest.h b/Framework/DataHandling/test/ApplyDiffCalTest.h
new file mode 100644
index 00000000000..d1a7f7dd19d
--- /dev/null
+++ b/Framework/DataHandling/test/ApplyDiffCalTest.h
@@ -0,0 +1,131 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2020 ISIS Rutherford Appleton Laboratory UKRI,
+//   NScD Oak Ridge National Laboratory, European Spallation Source,
+//   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
+// SPDX - License - Identifier: GPL - 3.0 +
+#pragma once
+
+#include <cxxtest/TestSuite.h>
+
+#include "MantidAPI/AnalysisDataService.h"
+#include "MantidAPI/TableRow.h"
+#include "MantidDataHandling/ApplyDiffCal.h"
+#include "MantidDataObjects/TableWorkspace.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidTestHelpers/ComponentCreationHelper.h"
+
+using namespace Mantid::API;
+using namespace Mantid::DataHandling;
+using namespace Mantid::DataObjects;
+
+namespace {
+const size_t NUM_BANK = 5;
+} // namespace
+
+class ApplyDiffCalTest : public CxxTest::TestSuite {
+public:
+  void testName() {
+    ApplyDiffCal appDiffCal;
+    TS_ASSERT_EQUALS(appDiffCal.name(), "ApplyDiffCal")
+  }
+
+  void testInit() {
+    ApplyDiffCal appDiffCal;
+    appDiffCal.initialize();
+    TS_ASSERT(appDiffCal.isInitialized())
+  }
+
+  void testExec() {
+    auto calWSIn = createCalibration(5 * 9); // nine components per bank
+
+    Mantid::Geometry::Instrument_sptr inst = createInstrument();
+
+    std::string testWorkspaceName = "TestApplyDiffCalWorkspace";
+    AnalysisDataService::Instance().add(testWorkspaceName,
+                                        std::make_shared<Workspace2D>());
+    auto instrumentWS =
+        AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(
+            testWorkspaceName);
+    instrumentWS->setInstrument(inst);
+
+    ApplyDiffCal appDiffCal;
+    TS_ASSERT_THROWS_NOTHING(appDiffCal.initialize());
+    TS_ASSERT(appDiffCal.isInitialized());
+    TS_ASSERT_THROWS_NOTHING(
+        appDiffCal.setProperty("InstrumentWorkspace", testWorkspaceName));
+    TS_ASSERT_THROWS_NOTHING(
+        appDiffCal.setProperty("CalibrationWorkspace", calWSIn));
+    TS_ASSERT_THROWS_NOTHING(appDiffCal.execute(););
+    TS_ASSERT(appDiffCal.isExecuted());
+
+    auto instFromWS = instrumentWS->getInstrument();
+    auto det = instFromWS->getDetector(3);
+    auto pmap = instFromWS->getParameterMap();
+    auto par = pmap->getRecursive(det.get(), "DIFC");
+    double difc{0.};
+    if (par)
+      difc = par->value<double>();
+    TS_ASSERT_EQUALS(difc, 102);
+    par = pmap->getRecursive(det.get(), "DIFA");
+    double difa{0.};
+    if (par)
+      difa = par->value<double>();
+    TS_ASSERT_EQUALS(difa, 4);
+    par = pmap->getRecursive(det.get(), "TZERO");
+    double tzero{0.};
+    if (par)
+      tzero = par->value<double>();
+    TS_ASSERT_EQUALS(tzero, 2);
+
+    ApplyDiffCal appDiffCalClear;
+    pmap->addDouble(det->getComponentID(), "extraparam", 1.23);
+    TS_ASSERT_THROWS_NOTHING(appDiffCalClear.initialize());
+    TS_ASSERT(appDiffCalClear.isInitialized());
+    TS_ASSERT_THROWS_NOTHING(
+        appDiffCalClear.setProperty("InstrumentWorkspace", testWorkspaceName));
+    TS_ASSERT_THROWS_NOTHING(
+        appDiffCalClear.setProperty("ClearCalibration", true));
+    TS_ASSERT_THROWS_NOTHING(appDiffCalClear.execute(););
+    TS_ASSERT(appDiffCalClear.isExecuted());
+
+    instFromWS = instrumentWS->getInstrument();
+    det = instFromWS->getDetector(3);
+    pmap = instFromWS->getParameterMap();
+    par = pmap->getRecursive(det.get(), "DIFC");
+    TS_ASSERT(!par);
+    par = pmap->getRecursive(det.get(), "extraparam");
+    TS_ASSERT(par);
+  }
+
+  void TestClear() {}
+
+private:
+  Mantid::Geometry::Instrument_sptr createInstrument() {
+    auto instr =
+        ComponentCreationHelper::createTestInstrumentCylindrical(NUM_BANK);
+    auto pmap = std::make_shared<Mantid::Geometry::ParameterMap>();
+    instr = std::make_shared<Mantid::Geometry::Instrument>(instr, pmap);
+    return instr;
+  }
+
+  TableWorkspace_sptr createCalibration(const size_t numRows) {
+    TableWorkspace_sptr wksp = std::make_shared<TableWorkspace>();
+    wksp->addColumn("int", "detid");
+    wksp->addColumn("double", "difc");
+    wksp->addColumn("double", "difa");
+    wksp->addColumn("double", "tzero");
+    wksp->addColumn("double", "tofmin");
+
+    for (size_t i = 0; i < numRows; ++i) {
+      TableRow row = wksp->appendRow();
+
+      row << static_cast<int>(i + 1)      // detid
+          << static_cast<double>(100 + i) // difc
+          << static_cast<double>(i * i)   // difa
+          << static_cast<double>(i)       // tzero
+          << 0.;                          // tofmin
+    }
+    return wksp;
+  }
+};
diff --git a/Framework/DataHandling/test/LoadGSSTest.h b/Framework/DataHandling/test/LoadGSSTest.h
index f38231405fc..7bdeb7771e5 100644
--- a/Framework/DataHandling/test/LoadGSSTest.h
+++ b/Framework/DataHandling/test/LoadGSSTest.h
@@ -97,6 +97,8 @@ public:
     TS_ASSERT_DELTA(spectrumInfo.twoTheta(0) * 180. / M_PI, 58.308, 1e-4);
     TS_ASSERT_DELTA(spectrumInfo.l2(1), 2.060, 1e-4);
     TS_ASSERT_DELTA(spectrumInfo.twoTheta(1) * 180. / M_PI, 154.257, 1e-4);
+    auto diffConsts = spectrumInfo.diffractometerConstants(1);
+    TS_ASSERT_DELTA(diffConsts[Kernel::UnitParams::difc], 10398.8, 1e-4);
   }
 
   void test_load_gss_ExtendedHeader_gsa() {
diff --git a/Framework/DataObjects/test/EventListTest.h b/Framework/DataObjects/test/EventListTest.h
index 697a6012932..468a1985c71 100644
--- a/Framework/DataObjects/test/EventListTest.h
+++ b/Framework/DataObjects/test/EventListTest.h
@@ -1486,8 +1486,8 @@ public:
   void test_convertUnitsViaTof_allTypes() {
     DummyUnit1 fromUnit;
     DummyUnit2 toUnit;
-    fromUnit.initialize(1, 2, 3, 4, 5, 6);
-    toUnit.initialize(1, 2, 3, 4, 5, 6);
+    fromUnit.initialize(1, 2, {});
+    toUnit.initialize(1, 2, {});
     // Go through each possible EventType as the input
     for (int this_type = 0; this_type < 3; this_type++) {
       this->fake_uniform_data();
diff --git a/Framework/Geometry/inc/MantidGeometry/Instrument/DetectorInfo.h b/Framework/Geometry/inc/MantidGeometry/Instrument/DetectorInfo.h
index b4c07f743e3..9c84b45c7d5 100644
--- a/Framework/Geometry/inc/MantidGeometry/Instrument/DetectorInfo.h
+++ b/Framework/Geometry/inc/MantidGeometry/Instrument/DetectorInfo.h
@@ -76,6 +76,11 @@ public:
   double signedTwoTheta(const std::pair<size_t, size_t> &index) const;
   double azimuthal(const size_t index) const;
   double azimuthal(const std::pair<size_t, size_t> &index) const;
+  std::tuple<double, double, double>
+  diffractometerConstants(const size_t index,
+                          std::vector<detid_t> &calibratedDets,
+                          std::vector<detid_t> &uncalibratedDets) const;
+  double difcUncalibrated(const size_t index) const;
   std::pair<double, double> geographicalAngles(const size_t index) const;
   std::pair<double, double>
   geographicalAngles(const std::pair<size_t, size_t> &index) const;
@@ -125,6 +130,7 @@ private:
   const Geometry::IDetector &getDetector(const size_t index) const;
   std::shared_ptr<const Geometry::IDetector>
   getDetectorPtr(const size_t index) const;
+  void clearPositionDependentParameters(const size_t index);
 
   /// Pointer to the actual DetectorInfo object (non-wrapping part).
   std::unique_ptr<Beamline::DetectorInfo> m_detectorInfo;
diff --git a/Framework/Geometry/inc/MantidGeometry/Instrument/ParameterMap.h b/Framework/Geometry/inc/MantidGeometry/Instrument/ParameterMap.h
index 7fb23a1ee2a..56f998a6edc 100644
--- a/Framework/Geometry/inc/MantidGeometry/Instrument/ParameterMap.h
+++ b/Framework/Geometry/inc/MantidGeometry/Instrument/ParameterMap.h
@@ -82,8 +82,10 @@ public:
   static const std::string &pQuat();
   static const std::string &scale();
 
-  const std::string diff(const ParameterMap &rhs,
-                         const bool &firstDiffOnly = false) const;
+  const std::string
+  diff(const ParameterMap &rhs, const bool &firstDiffOnly = false,
+       const bool relative = false,
+       const double doubleTolerance = Kernel::Tolerance) const;
 
   /// Inquality comparison operator
   bool operator!=(const ParameterMap &rhs) const;
@@ -346,6 +348,8 @@ private:
   /// the parameter map
   component_map_cit positionOf(const IComponent *comp, const char *name,
                                const char *type) const;
+  /// calculate relative error for use in diff
+  bool relErr(double x1, double x2, double errorVal) const;
 
   /// internal list of parameter files loaded
   std::vector<std::string> m_parameterFileNames;
diff --git a/Framework/Geometry/src/Instrument.cpp b/Framework/Geometry/src/Instrument.cpp
index edc2af5db14..3b2694bcd2c 100644
--- a/Framework/Geometry/src/Instrument.cpp
+++ b/Framework/Geometry/src/Instrument.cpp
@@ -20,6 +20,7 @@
 #include "MantidKernel/Exception.h"
 #include "MantidKernel/Logger.h"
 #include "MantidKernel/PhysicalConstants.h"
+#include "MantidKernel/Unit.h"
 
 #include <memory>
 #include <nexus/NeXusFile.hpp>
@@ -858,9 +859,6 @@ void Instrument::appendPlottable(
   }
 }
 
-const double CONSTANT = (PhysicalConstants::h * 1e10) /
-                        (2.0 * PhysicalConstants::NeutronMass * 1e6);
-
 //------------------------------------------------------------------------------------------------
 /** Get several instrument parameters used in tof to D-space conversion
  *
@@ -1365,50 +1363,9 @@ namespace Conversion {
  */
 double tofToDSpacingFactor(const double l1, const double l2,
                            const double twoTheta, const double offset) {
-  if (offset <=
-      -1.) // not physically possible, means result is negative d-spacing
-  {
-    std::stringstream msg;
-    msg << "Encountered offset of " << offset
-        << " which converts data to negative d-spacing\n";
-    throw std::logic_error(msg.str());
-  }
-
-  auto sinTheta = std::sin(twoTheta / 2);
-
-  const double numerator = (1.0 + offset);
-  sinTheta *= (l1 + l2);
-
-  return (numerator * CONSTANT) / sinTheta;
+  return Kernel::Units::tofToDSpacingFactor(l1, l2, twoTheta, offset);
 }
 
-/** Calculate the conversion factor from tof -> d-spacing
- * for a LIST of detector ids assigned to a single spectrum.
- * @brief tofToDSpacingFactor
- * @param l1
- * @param l2
- * @param twoTheta scattering angle
- * @param detectors
- * @param offsets
- * @return
- */
-double tofToDSpacingFactor(const double l1, const double l2,
-                           const double twoTheta,
-                           const std::vector<detid_t> &detectors,
-                           const std::map<detid_t, double> &offsets) {
-  double factor = 0.;
-  double offset;
-  for (auto detector : detectors) {
-    auto off_iter = offsets.find(detector);
-    if (off_iter != offsets.cend()) {
-      offset = offsets.find(detector)->second;
-    } else {
-      offset = 0.;
-    }
-    factor += tofToDSpacingFactor(l1, l2, twoTheta, offset);
-  }
-  return factor / static_cast<double>(detectors.size());
-}
 } // namespace Conversion
 } // namespace Geometry
 } // Namespace Mantid
diff --git a/Framework/Geometry/src/Instrument/DetectorInfo.cpp b/Framework/Geometry/src/Instrument/DetectorInfo.cpp
index bda1593cc53..3ce2a14be11 100644
--- a/Framework/Geometry/src/Instrument/DetectorInfo.cpp
+++ b/Framework/Geometry/src/Instrument/DetectorInfo.cpp
@@ -15,6 +15,7 @@
 #include "MantidKernel/EigenConversionHelpers.h"
 #include "MantidKernel/Exception.h"
 #include "MantidKernel/MultiThreaded.h"
+#include "MantidKernel/Unit.h"
 
 namespace Mantid {
 namespace Geometry {
@@ -308,6 +309,37 @@ double DetectorInfo::azimuthal(const std::pair<size_t, size_t> &index) const {
   return atan2(dotVertical, dotHorizontal);
 }
 
+std::tuple<double, double, double> DetectorInfo::diffractometerConstants(
+    const size_t index, std::vector<detid_t> &calibratedDets,
+    std::vector<detid_t> &uncalibratedDets) const {
+  auto det = m_instrument->getDetector((*m_detectorIDs)[index]);
+  auto pmap = m_instrument->getParameterMap();
+  auto par = pmap->get(det.get(), "DIFC");
+  if (par) {
+    double difc = par->value<double>();
+    calibratedDets.push_back((*m_detectorIDs)[index]);
+    double difa = 0., tzero = 0.;
+    par = pmap->get(det.get(), "DIFA");
+    if (par)
+      difa = par->value<double>();
+    par = pmap->get(det.get(), "TZERO");
+    if (par)
+      tzero = par->value<double>();
+    return {difa, difc, tzero};
+  } else {
+    // if calibrated difc not available, revert to uncalibrated difc with other
+    // two constants=0
+    uncalibratedDets.push_back((*m_detectorIDs)[index]);
+    double difc = difcUncalibrated(index);
+    return {0., difc, 0.};
+  }
+}
+
+double DetectorInfo::difcUncalibrated(const size_t index) const {
+  return 1. / Kernel::Units::tofToDSpacingFactor(l1(), l2(index),
+                                                 twoTheta(index), 0.);
+}
+
 std::pair<double, double>
 DetectorInfo::geographicalAngles(const size_t index) const {
   const auto samplePos = samplePosition();
@@ -383,15 +415,28 @@ void DetectorInfo::clearMaskFlags() {
 /// Set the absolute position of the detector with given index. Not thread safe.
 void DetectorInfo::setPosition(const size_t index,
                                const Kernel::V3D &position) {
+
+  clearPositionDependentParameters(index);
   m_detectorInfo->setPosition(index, Kernel::toVector3d(position));
 }
 
 /// Set the absolute position of the detector with given index. Not thread safe.
 void DetectorInfo::setPosition(const std::pair<size_t, size_t> &index,
                                const Kernel::V3D &position) {
+  clearPositionDependentParameters(index.first);
   m_detectorInfo->setPosition(index, Kernel::toVector3d(position));
 }
 
+// Clear any parameters whose value is only valid for specific positions
+// Currently diffractometer constants
+void DetectorInfo::clearPositionDependentParameters(const size_t index) {
+  auto det = m_instrument->getDetector((*m_detectorIDs)[index]);
+  auto pmap = m_instrument->getParameterMap();
+  pmap->clearParametersByName("DIFA", det.get());
+  pmap->clearParametersByName("DIFC", det.get());
+  pmap->clearParametersByName("TZERO", det.get());
+}
+
 /// Set the absolute rotation of the detector with given index. Not thread safe.
 void DetectorInfo::setRotation(const size_t index,
                                const Kernel::Quat &rotation) {
diff --git a/Framework/Geometry/src/Instrument/ParameterMap.cpp b/Framework/Geometry/src/Instrument/ParameterMap.cpp
index 05b2d805e41..4b6ff507b18 100644
--- a/Framework/Geometry/src/Instrument/ParameterMap.cpp
+++ b/Framework/Geometry/src/Instrument/ParameterMap.cpp
@@ -138,36 +138,7 @@ bool ParameterMap::operator!=(const ParameterMap &rhs) const {
  * @return true if the objects are considered equal, false otherwise
  */
 bool ParameterMap::operator==(const ParameterMap &rhs) const {
-  if (this == &rhs)
-    return true; // True for the same object
-
-  // Quick size check
-  if (this->size() != rhs.size())
-    return false;
-
-  // The map is unordered and the key is only valid at runtime. The
-  // asString method turns the ComponentIDs to full-qualified name identifiers
-  // so we will use the same approach to compare them
-
-  auto thisEnd = this->m_map.cend();
-  auto rhsEnd = rhs.m_map.cend();
-  for (auto thisIt = this->m_map.begin(); thisIt != thisEnd; ++thisIt) {
-    const IComponent *comp = static_cast<IComponent *>(thisIt->first);
-    const std::string fullName = comp->getFullName();
-    const auto &param = thisIt->second;
-    bool match(false);
-    for (auto rhsIt = rhs.m_map.cbegin(); rhsIt != rhsEnd; ++rhsIt) {
-      const IComponent *rhsComp = static_cast<IComponent *>(rhsIt->first);
-      const std::string rhsFullName = rhsComp->getFullName();
-      if (fullName == rhsFullName && (*param) == (*rhsIt->second)) {
-        match = true;
-        break;
-      }
-    }
-    if (!match)
-      return false;
-  }
-  return true;
+  return diff(rhs, true, false, 0.).empty();
 }
 
 /** Get the component description by name
@@ -217,6 +188,31 @@ ParameterMap::getShortDescription(const std::string &compName,
   }
   return result;
 }
+
+//------------------------------------------------------------------------------------------------
+/** Function which calculates relative error between two values and analyses if
+this error is within the limits
+* requested. When the absolute value of the difference is smaller then the value
+of the error requested,
+* absolute error is used instead of relative error.
+
+@param x1       -- first value to check difference
+@param x2       -- second value to check difference
+@param errorVal -- the value of the error, to check against. Should  be large
+then 0
+
+@returns true if error or false if the value is within the limits requested
+*/
+bool ParameterMap::relErr(double x1, double x2, double errorVal) const {
+  double num = std::fabs(x1 - x2);
+  // how to treat x1<0 and x2 > 0 ?  probably this way
+  double den = 0.5 * (std::fabs(x1) + std::fabs(x2));
+  if (den < errorVal)
+    return (num > errorVal);
+
+  return (num / den > errorVal);
+}
+
 /**
  * Output information that helps understanding the mismatch between two
  * parameter maps.
@@ -226,10 +222,15 @@ ParameterMap::getShortDescription(const std::string &compName,
  * true
  * @param rhs A reference to a ParameterMap object to compare it to
  * @param firstDiffOnly If true return only first difference found
+ * @param relative Indicates whether to treat the error as relative or absolute
+ * @param doubleTolerance The tolerance to use when comparing parameter values
+ * of type double
  * @return diff as a string
  */
 const std::string ParameterMap::diff(const ParameterMap &rhs,
-                                     const bool &firstDiffOnly) const {
+                                     const bool &firstDiffOnly,
+                                     const bool relative,
+                                     const double doubleTolerance) const {
   if (this == &rhs)
     return std::string(""); // True for the same object
 
@@ -240,25 +241,41 @@ const std::string ParameterMap::diff(const ParameterMap &rhs,
            std::to_string(rhs.size());
   }
 
-  // Run this same loops as in operator==
   // The map is unordered and the key is only valid at runtime. The
   // asString method turns the ComponentIDs to full-qualified name identifiers
   // so we will use the same approach to compare them
 
+  std::unordered_multimap<std::string, Parameter_sptr> thisMap, rhsMap;
+  for (auto &mappair : this->m_map) {
+    thisMap.emplace(mappair.first->getFullName(), mappair.second);
+  }
+  for (auto &mappair : rhs.m_map) {
+    rhsMap.emplace(mappair.first->getFullName(), mappair.second);
+  }
+
   std::stringstream strOutput;
-  auto thisEnd = this->m_map.cend();
-  auto rhsEnd = rhs.m_map.cend();
-  for (auto thisIt = this->m_map.cbegin(); thisIt != thisEnd; ++thisIt) {
-    const IComponent *comp = static_cast<IComponent *>(thisIt->first);
-    const std::string fullName = comp->getFullName();
+  for (auto thisIt = thisMap.cbegin(); thisIt != thisMap.cend(); ++thisIt) {
+    const std::string fullName = thisIt->first;
     const auto &param = thisIt->second;
     bool match(false);
-    for (auto rhsIt = rhs.m_map.cbegin(); rhsIt != rhsEnd; ++rhsIt) {
-      const IComponent *rhsComp = static_cast<IComponent *>(rhsIt->first);
-      const std::string rhsFullName = rhsComp->getFullName();
-      if (fullName == rhsFullName && (*param) == (*rhsIt->second)) {
-        match = true;
-        break;
+    for (auto rhsIt = rhsMap.cbegin(); rhsIt != rhsMap.cend(); ++rhsIt) {
+      const std::string rhsFullName = rhsIt->first;
+      const auto &rhsParam = rhsIt->second;
+      if ((fullName == rhsFullName) && (param->name() == (rhsParam->name()))) {
+        if ((param->type() == rhsParam->type()) &&
+            (rhsParam->type() == "double")) {
+          if (relative) {
+            if (!relErr(param->value<double>(), rhsParam->value<double>(),
+                        doubleTolerance))
+              match = true;
+          } else if (std::abs(param->value<double>() -
+                              rhsParam->value<double>()) <= doubleTolerance)
+            match = true;
+        } else if (param->asString() == rhsParam->asString()) {
+          match = true;
+        }
+        if (match)
+          break;
       }
     }
 
@@ -270,9 +287,8 @@ const std::string ParameterMap::diff(const ParameterMap &rhs,
                 << " and value: " << (*param).asString() << '\n';
       bool componentWithSameNameRHS = false;
       bool parameterWithSameNameRHS = false;
-      for (auto rhsIt = rhs.m_map.cbegin(); rhsIt != rhsEnd; ++rhsIt) {
-        const IComponent *rhsComp = static_cast<IComponent *>(rhsIt->first);
-        const std::string rhsFullName = rhsComp->getFullName();
+      for (auto rhsIt = rhsMap.cbegin(); rhsIt != rhsMap.cend(); ++rhsIt) {
+        const std::string rhsFullName = rhsIt->first;
         if (fullName == rhsFullName) {
           componentWithSameNameRHS = true;
           if ((*param).name() == (*rhsIt->second).name()) {
diff --git a/Framework/Kernel/CMakeLists.txt b/Framework/Kernel/CMakeLists.txt
index fc5097d760f..483280d5113 100644
--- a/Framework/Kernel/CMakeLists.txt
+++ b/Framework/Kernel/CMakeLists.txt
@@ -23,7 +23,6 @@ set(SRC_FILES
     src/DateTimeValidator.cpp
     src/DateValidator.cpp
     src/DeltaEMode.cpp
-    src/Diffraction.cpp
     src/DirectoryValidator.cpp
     src/DiskBuffer.cpp
     src/DllOpen.cpp
@@ -173,7 +172,6 @@ set(INC_FILES
     inc/MantidKernel/DateTimeValidator.h
     inc/MantidKernel/DateValidator.h
     inc/MantidKernel/DeltaEMode.h
-    inc/MantidKernel/Diffraction.h
     inc/MantidKernel/DirectoryValidator.h
     inc/MantidKernel/DiskBuffer.h
     inc/MantidKernel/DllConfig.h
@@ -343,7 +341,6 @@ set(TEST_FILES
     DateTimeValidatorTest.h
     DateValidatorTest.h
     DeltaEModeTest.h
-    DiffractionTest.h
     DirectoryValidatorTest.h
     DiskBufferISaveableTest.h
     DiskBufferTest.h
diff --git a/Framework/Kernel/inc/MantidKernel/Diffraction.h b/Framework/Kernel/inc/MantidKernel/Diffraction.h
deleted file mode 100644
index a75c1f0e910..00000000000
--- a/Framework/Kernel/inc/MantidKernel/Diffraction.h
+++ /dev/null
@@ -1,36 +0,0 @@
-// Mantid Repository : https://github.com/mantidproject/mantid
-//
-// Copyright &copy; 2017 ISIS Rutherford Appleton Laboratory UKRI,
-//   NScD Oak Ridge National Laboratory, European Spallation Source,
-//   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
-// SPDX - License - Identifier: GPL - 3.0 +
-#pragma once
-
-#include "MantidKernel/DllConfig.h"
-#include <functional>
-
-namespace Mantid {
-namespace Kernel {
-namespace Diffraction {
-
-/** Diffraction : Collection of functions useful in diffraction scattering
- */
-
-MANTID_KERNEL_DLL double calcTofMin(const double difc, const double difa,
-                                    const double tzero,
-                                    const double tofmin = 0.);
-
-MANTID_KERNEL_DLL double calcTofMax(const double difc, const double difa,
-                                    const double tzero, const double tofmax);
-
-MANTID_KERNEL_DLL std::function<double(double)>
-getTofToDConversionFunc(const double difc, const double difa,
-                        const double tzero);
-
-MANTID_KERNEL_DLL std::function<double(double)>
-getDToTofConversionFunc(const double difc, const double difa,
-                        const double tzero);
-
-} // namespace Diffraction
-} // namespace Kernel
-} // namespace Mantid
diff --git a/Framework/Kernel/inc/MantidKernel/Unit.h b/Framework/Kernel/inc/MantidKernel/Unit.h
index 3e6cee072f9..8537b8b815a 100644
--- a/Framework/Kernel/inc/MantidKernel/Unit.h
+++ b/Framework/Kernel/inc/MantidKernel/Unit.h
@@ -12,6 +12,7 @@
 #include "MantidKernel/UnitLabel.h"
 #include <utility>
 
+#include <unordered_map>
 #include <vector>
 #ifndef Q_MOC_RUN
 #include <memory>
@@ -22,6 +23,12 @@
 namespace Mantid {
 namespace Kernel {
 
+enum class UnitParams { l2, twoTheta, efixed, delta, difa, difc, tzero };
+
+// list of parameter names and values - some code may relay on map behaviour
+// where [] creates element with value 0 if param name not present
+using UnitParametersMap = std::unordered_map<UnitParams, double>;
+
 /** The base units (abstract) class. All concrete units should inherit from
     this class and provide implementations of the caption(), label(),
     toTOF() and fromTOF() methods. They also need to declare (but NOT define)
@@ -75,87 +82,90 @@ public:
    *  @param ydata ::    Not currently used (ConvertUnits passes an empty
    * vector)
    *  @param _l1 ::       The source-sample distance (in metres)
-   *  @param _l2 ::       The sample-detector distance (in metres)
-   *  @param _twoTheta :: The scattering angle (in radians)
    *  @param _emode ::    The energy mode (0=elastic, 1=direct geometry,
    * 2=indirect geometry)
-   *  @param _efixed ::   Value of fixed energy: EI (emode=1) or EF (emode=2)
-   * (in
-   * meV)
-   *  @param _delta ::    Not currently used
+   *  @param params ::  Map containing optional parameters eg
+   *                    The sample-detector distance (in metres)
+   *                    The scattering angle (in radians)
+   *                    Fixed energy: EI (emode=1) or EF (emode=2)(in meV)
+   *                    Delta (not currently used)
    */
   void toTOF(std::vector<double> &xdata, std::vector<double> &ydata,
-             const double &_l1, const double &_l2, const double &_twoTheta,
-             const int &_emode, const double &_efixed, const double &_delta);
+             const double &_l1, const int &_emode,
+             std::initializer_list<std::pair<const UnitParams, double>> params);
+  void toTOF(std::vector<double> &xdata, std::vector<double> &ydata,
+             const double &_l1, const int &_emode,
+             const UnitParametersMap &params);
 
   /** Convert from the concrete unit to time-of-flight. TOF is in microseconds.
    *  @param xvalue ::   A single X-value to convert
    *  @param l1 ::       The source-sample distance (in metres)
-   *  @param l2 ::       The sample-detector distance (in metres)
-   *  @param twoTheta :: The scattering angle (in radians)
    *  @param emode ::    The energy mode (0=elastic, 1=direct geometry,
    * 2=indirect geometry)
-   *  @param efixed ::   Value of fixed energy: EI (emode=1) or EF (emode=2) (in
-   * meV)
-   *  @param delta ::    Not currently used
+   *  @param params ::  Map containing optional parameters eg
+   *                    The sample-detector distance (in metres)
+   *                    The scattering angle (in radians)
+   *                    Fixed energy: EI (emode=1) or EF (emode=2)(in meV)
+   *                    Delta (not currently used)
    *  @return the value in TOF units.
    */
   double convertSingleToTOF(const double xvalue, const double &l1,
-                            const double &l2, const double &twoTheta,
-                            const int &emode, const double &efixed,
-                            const double &delta);
+                            const int &emode, const UnitParametersMap &params);
 
   /** Convert from time-of-flight to the concrete unit. TOF is in microseconds.
    *  @param xdata ::    The array of X data to be converted
    *  @param ydata ::    Not currently used (ConvertUnits passes an empty
    * vector)
    *  @param _l1 ::       The source-sample distance (in metres)
-   *  @param _l2 ::       The sample-detector distance (in metres)
-   *  @param _twoTheta :: The scattering angle (in radians)
    *  @param _emode ::    The energy mode (0=elastic, 1=direct geometry,
    * 2=indirect geometry)
-   *  @param _efixed ::   Value of fixed energy: EI (emode=1) or EF (emode=2)
-   * (in
-   * meV)
-   *  @param _delta ::    Not currently used
+   *  @param params ::  Map containing optional parameters eg
+   *                    The sample-detector distance (in metres)
+   *                    The scattering angle (in radians)
+   *                    Fixed energy: EI (emode=1) or EF (emode=2)(in meV)
+   *                    Delta (not currently used)
    */
+  void
+  fromTOF(std::vector<double> &xdata, std::vector<double> &ydata,
+          const double &_l1, const int &_emode,
+          std::initializer_list<std::pair<const UnitParams, double>> params);
+
   void fromTOF(std::vector<double> &xdata, std::vector<double> &ydata,
-               const double &_l1, const double &_l2, const double &_twoTheta,
-               const int &_emode, const double &_efixed, const double &_delta);
+               const double &_l1, const int &_emode,
+               const UnitParametersMap &params);
 
   /** Convert from the time-of-flight to the concrete unit. TOF is in
    * microseconds.
    *  @param xvalue ::   A single X-value to convert
    *  @param l1 ::       The source-sample distance (in metres)
-   *  @param l2 ::       The sample-detector distance (in metres)
-   *  @param twoTheta :: The scattering angle (in radians)
    *  @param emode ::    The energy mode (0=elastic, 1=direct geometry,
    * 2=indirect geometry)
-   *  @param efixed ::   Value of fixed energy: EI (emode=1) or EF (emode=2) (in
-   * meV)
-   *  @param delta ::    Not currently used
+   *  @param params ::  Map containing optional parameters eg
+   *                    The sample-detector distance (in metres)
+   *                    The scattering angle (in radians)
+   *                    Fixed energy: EI (emode=1) or EF (emode=2)(in meV)
+   *                    Delta (not currently used)
    *  @return the value in these units.
    */
   double convertSingleFromTOF(const double xvalue, const double &l1,
-                              const double &l2, const double &twoTheta,
-                              const int &emode, const double &efixed,
-                              const double &delta);
+                              const int &emode,
+                              const UnitParametersMap &params);
 
   /** Initialize the unit to perform conversion using singleToTof() and
    *singleFromTof()
    *
    *  @param _l1 ::       The source-sample distance (in metres)
-   *  @param _l2 ::       The sample-detector distance (in metres)
-   *  @param _twoTheta :: The scattering angle (in radians)
    *  @param _emode ::    The energy mode (0=elastic, 1=direct geometry,
    *2=indirect geometry)
-   *  @param _efixed ::   Value of fixed energy: EI (emode=1) or EF (emode=2)
-   *(in meV)
-   *  @param _delta ::    Not currently used
+   *  @param params ::    map containing other optional parameters:
+   *                      The sample-detector distance (in metres)
+   *                      The scattering angle (in radians)
+   *                      Fixed energy: EI (emode=1) or EF (emode=2) (in meV)
+   *                      Diffractometer constants (DIFA, DIFC, TZERO)
+   *                      Delta: unused
    */
-  void initialize(const double &_l1, const double &_l2, const double &_twoTheta,
-                  const int &_emode, const double &_efixed,
-                  const double &_delta);
+  void initialize(const double &_l1, const int &_emode,
+                  const UnitParametersMap &params);
 
   /** Finalize the initialization. This will be overridden by subclasses as
    * needed. */
@@ -194,21 +204,25 @@ protected:
   void addConversion(std::string to, const double &factor,
                      const double &power = 1.0) const;
 
+  // validate the contents of the unit parameters map. Throw
+  // std::invalid_argument if it's a global error or std::runtime_error if it's
+  // a detector specific error
+  virtual void validateUnitParams(const int emode,
+                                  const UnitParametersMap &params);
+
   /// The unit values have been initialized
   bool initialized;
   /// l1 ::       The source-sample distance (in metres)
   double l1;
-  /// l2 ::       The sample-detector distance (in metres)
-  double l2;
-  /// twoTheta :: The scattering angle (in radians)
-  double twoTheta;
   /// emode ::    The energy mode (0=elastic, 1=direct geometry, 2=indirect
   /// geometry)
   int emode;
+  /// additional parameters
+  /// l2 :: distance from sample to detector (in metres)
+  /// twoTheta :: scattering angle in radians
   /// efixed ::   Value of fixed energy: EI (emode=1) or EF (emode=2) (in meV)
-  double efixed;
-  /// delta ::    Not currently used
-  double delta;
+  /// difc :: diffractometer constant DIFC
+  const UnitParametersMap *m_params;
 
 private:
   /// A 'quick conversion' requires the constant by which to multiply the input
@@ -318,6 +332,9 @@ public:
   Wavelength();
 
 protected:
+  void validateUnitParams(const int emode,
+                          const UnitParametersMap &params) override;
+  double efixed;
   double sfpTo;      ///< Extra correction factor in to conversion
   double factorTo;   ///< Constant factor for to conversion
   double sfpFrom;    ///< Extra correction factor in from conversion
@@ -368,10 +385,16 @@ public:
   Energy_inWavenumber();
 
 protected:
+  void validateUnitParams(const int emode,
+                          const UnitParametersMap &params) override;
   double factorTo;   ///< Constant factor for to conversion
   double factorFrom; ///< Constant factor for from conversion
 };
 
+MANTID_KERNEL_DLL double tofToDSpacingFactor(const double l1, const double l2,
+                                             const double twoTheta,
+                                             const double offset);
+
 //=================================================================================================
 /// d-Spacing in Angstrom
 class MANTID_KERNEL_DLL dSpacing : public Unit {
@@ -379,20 +402,27 @@ public:
   const std::string unitID() const override; ///< "dSpacing"
   const std::string caption() const override { return "d-Spacing"; }
   const UnitLabel label() const override;
-
   double singleToTOF(const double x) const override;
   double singleFromTOF(const double tof) const override;
   void init() override;
   Unit *clone() const override;
   double conversionTOFMin() const override;
   double conversionTOFMax() const override;
+  double calcTofMin(const double difc, const double difa, const double tzero,
+                    const double tofmin = 0.);
+  double calcTofMax(const double difc, const double difa, const double tzero,
+                    const double tofmax = 0.);
 
   /// Constructor
   dSpacing();
 
 protected:
-  double factorTo;   ///< Constant factor for to conversion
-  double factorFrom; ///< Constant factor for from conversion
+  void validateUnitParams(const int emode,
+                          const UnitParametersMap &params) override;
+  std::string toDSpacingError;
+  double difa;
+  double difc;
+  double tzero;
 };
 
 //=================================================================================================
@@ -416,6 +446,9 @@ public:
   dSpacingPerpendicular();
 
 protected:
+  void validateUnitParams(const int emode,
+                          const UnitParametersMap &params) override;
+  double twoTheta;
   double factorTo;   ///< Constant factor for to conversion
   double sfpTo;      ///< Extra correction factor in to conversion
   double factorFrom; ///< Constant factor for from conversion
@@ -440,13 +473,14 @@ public:
   MomentumTransfer();
 
 protected:
-  double factorTo;   ///< Constant factor for to conversion
-  double factorFrom; ///< Constant factor for from conversion
+  void validateUnitParams(const int emode,
+                          const UnitParametersMap &params) override;
+  double difc;
 };
 
 //=================================================================================================
 /// Momentum transfer squared in Angstrom^-2
-class MANTID_KERNEL_DLL QSquared : public Unit {
+class MANTID_KERNEL_DLL QSquared : public MomentumTransfer {
 public:
   const std::string unitID() const override; ///< "QSquared"
   const std::string caption() const override { return "Q2"; }
@@ -454,17 +488,12 @@ public:
 
   double singleToTOF(const double x) const override;
   double singleFromTOF(const double tof) const override;
-  void init() override;
   Unit *clone() const override;
   double conversionTOFMin() const override;
   double conversionTOFMax() const override;
 
   /// Constructor
   QSquared();
-
-protected:
-  double factorTo;   ///< Constant factor for to conversion
-  double factorFrom; ///< Constant factor for from conversion
 };
 
 //=================================================================================================
@@ -487,6 +516,9 @@ public:
   DeltaE();
 
 protected:
+  void validateUnitParams(const int emode,
+                          const UnitParametersMap &params) override;
+  double efixed;
   double factorTo;    ///< Constant factor for to conversion
   double factorFrom;  ///< Constant factor for from conversion
   double t_other;     ///< Energy mode dependent factor in to conversion
@@ -545,6 +577,9 @@ public:
   Momentum();
 
 protected:
+  void validateUnitParams(const int emode,
+                          const UnitParametersMap &params) override;
+  double efixed;
   double sfpTo;      ///< Extra correction factor in to conversion
   double factorTo;   ///< Constant factor for to conversion
   double sfpFrom;    ///< Extra correction factor in from conversion
@@ -569,6 +604,9 @@ public:
 
   /// Constructor
   SpinEchoLength();
+
+private:
+  double efixed;
 };
 
 //=================================================================================================
@@ -588,6 +626,9 @@ public:
 
   /// Constructor
   SpinEchoTime();
+
+private:
+  double efixed;
 };
 
 //=================================================================================================
diff --git a/Framework/Kernel/inc/MantidKernel/UnitConversion.h b/Framework/Kernel/inc/MantidKernel/UnitConversion.h
index 327c4f8bdb5..dc11da623d1 100644
--- a/Framework/Kernel/inc/MantidKernel/UnitConversion.h
+++ b/Framework/Kernel/inc/MantidKernel/UnitConversion.h
@@ -8,6 +8,7 @@
 
 #include "MantidKernel/DeltaEMode.h"
 #include "MantidKernel/DllConfig.h"
+#include "MantidKernel/Unit.h"
 #include <string>
 
 namespace Mantid {
@@ -27,10 +28,14 @@ public:
                     const double srcValue, const double l1, const double l2,
                     const double theta, const DeltaEMode::Type emode,
                     const double efixed);
+  static double run(const std::string &src, const std::string &dest,
+                    const double srcValue, const double l1,
+                    const DeltaEMode::Type emode,
+                    const UnitParametersMap &params = {});
   /// Convert a single value between the given units
   static double run(Unit &srcUnit, Unit &destUnit, const double srcValue,
-                    const double l1, const double l2, const double theta,
-                    const DeltaEMode::Type emode, const double efixed);
+                    const double l1, const DeltaEMode::Type emode,
+                    const UnitParametersMap &params = {});
 
   /// Convert to ElasticQ from Energy
   static double convertToElasticQ(const double theta, const double efixed);
@@ -42,9 +47,8 @@ private:
   /// Convert through TOF
   static double convertViaTOF(Unit &srcUnit, Unit &destUnit,
                               const double srcValue, const double l1,
-                              const double l2, const double theta,
                               const DeltaEMode::Type emode,
-                              const double efixed);
+                              const UnitParametersMap &params);
 };
 
 } // namespace Kernel
diff --git a/Framework/Kernel/src/Diffraction.cpp b/Framework/Kernel/src/Diffraction.cpp
deleted file mode 100644
index 1c9e4d64d88..00000000000
--- a/Framework/Kernel/src/Diffraction.cpp
+++ /dev/null
@@ -1,173 +0,0 @@
-// 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 & CSNS, Institute of High Energy Physics, CAS
-// SPDX - License - Identifier: GPL - 3.0 +
-#include "MantidKernel/Diffraction.h"
-#include <algorithm>
-#include <cmath>
-
-namespace Mantid {
-namespace Kernel {
-namespace Diffraction {
-
-double calcTofMin(const double difc, const double difa, const double tzero,
-                  const double tofmin) {
-  if (difa == 0.) {
-    if (tzero != 0.) {
-      // check for negative d-spacing
-      return std::max<double>(tzero, tofmin);
-    }
-  } else if (difa > 0.) {
-    // check for imaginary part in quadratic equation
-    return std::max<double>(tzero - .25 * difc * difc / difa, tofmin);
-  }
-
-  // everything else is fine so just return supplied tofmin
-  return tofmin;
-}
-
-/**
- * Returns the maximum TOF that can be used or tofmax. Whichever is smaller. In
- * the case when this is a negative number, just return 0.
- */
-double calcTofMax(const double difc, const double difa, const double tzero,
-                  const double tofmax) {
-  if (difa < 0.) {
-    // check for imaginary part in quadratic equation
-    if (tzero > 0.) {
-      // rather than calling abs multiply difa by -1
-      return std::min<double>(tzero + .25 * difc * difc / difa, tofmax);
-    } else {
-      return 0.;
-    }
-  }
-
-  // everything else is fine so just return supplied tofmax
-  return tofmax;
-}
-
-// ----------------------------------------------------------------------------
-// convert from d-spacing to time-of-flight
-namespace { // anonymous namespace
-/// Applies the equation d=(TOF-tzero)/difc
-struct tof_to_d_difc_only {
-  explicit tof_to_d_difc_only(const double difc) : factor(1. / difc) {}
-
-  double operator()(const double tof) const { return factor * tof; }
-
-  /// 1./difc
-  double factor;
-};
-
-/// Applies the equation d=(TOF-tzero)/difc
-struct tof_to_d_difc_and_tzero {
-  explicit tof_to_d_difc_and_tzero(const double difc, const double tzero)
-      : factor(1. / difc), offset(-1. * tzero / difc) {}
-
-  double operator()(const double tof) const { return factor * tof + offset; }
-
-  /// 1./difc
-  double factor;
-  /// -tzero/difc
-  double offset;
-};
-
-struct tof_to_d {
-  explicit tof_to_d(const double difc, const double difa, const double tzero) {
-    factor1 = -0.5 * difc / difa;
-    factor2 = 1. / difa;
-    factor3 = (factor1 * factor1) - (tzero / difa);
-  }
-
-  double operator()(const double tof) const {
-    double second = std::sqrt((tof * factor2) + factor3);
-    if (second < factor1)
-      return factor1 - second;
-    else {
-      return factor1 + second;
-    }
-  }
-
-  /// -0.5*difc/difa
-  double factor1;
-  /// 1/difa
-  double factor2;
-  /// (0.5*difc/difa)^2 - (tzero/difa)
-  double factor3;
-};
-} // anonymous namespace
-
-std::function<double(double)> getTofToDConversionFunc(const double difc,
-                                                      const double difa,
-                                                      const double tzero) {
-  if (difa == 0.) {
-    if (tzero == 0.) {
-      return tof_to_d_difc_only(difc);
-    } else {
-      return tof_to_d_difc_and_tzero(difc, tzero);
-    }
-  } else { // difa != 0.
-    return tof_to_d(difc, difa, tzero);
-  }
-}
-
-// ----------------------------------------------------------------------------
-// convert from d-spacing to time-of-flight
-namespace { // anonymous namespace
-struct d_to_tof_difc_only {
-  explicit d_to_tof_difc_only(const double difc) { this->difc = difc; }
-
-  double operator()(const double dspacing) const { return difc * dspacing; }
-
-  double difc;
-};
-
-struct d_to_tof_difc_and_tzero {
-  explicit d_to_tof_difc_and_tzero(const double difc, const double tzero) {
-    this->difc = difc;
-    this->tzero = tzero;
-  }
-
-  double operator()(const double dspacing) const {
-    return difc * dspacing + tzero;
-  }
-
-  double difc;
-  double tzero;
-};
-
-struct d_to_tof {
-  explicit d_to_tof(const double difc, const double difa, const double tzero) {
-    this->difc = difc;
-    this->difa = difa;
-    this->tzero = tzero;
-  }
-
-  double operator()(const double dspacing) const {
-    return difc * dspacing + difa * dspacing * dspacing + tzero;
-  }
-
-  double difc;
-  double difa;
-  double tzero;
-};
-} // anonymous namespace
-
-std::function<double(double)> getDToTofConversionFunc(const double difc,
-                                                      const double difa,
-                                                      const double tzero) {
-  if (difa == 0.) {
-    if (tzero == 0.)
-      return d_to_tof_difc_only(difc);
-    else
-      return d_to_tof_difc_and_tzero(difc, tzero);
-  } else {
-    return d_to_tof(difc, difa, tzero);
-  }
-}
-
-} // namespace Diffraction
-} // namespace Kernel
-} // namespace Mantid
diff --git a/Framework/Kernel/src/Unit.cpp b/Framework/Kernel/src/Unit.cpp
index b74bedb91b1..5c831c7b9b1 100644
--- a/Framework/Kernel/src/Unit.cpp
+++ b/Framework/Kernel/src/Unit.cpp
@@ -8,21 +8,41 @@
 // Includes
 //----------------------------------------------------------------------
 #include "MantidKernel/Unit.h"
+#include "MantidKernel/Logger.h"
 #include "MantidKernel/PhysicalConstants.h"
 #include "MantidKernel/UnitFactory.h"
 #include "MantidKernel/UnitLabelTypes.h"
 #include <cfloat>
+#include <sstream>
 
 namespace Mantid {
 namespace Kernel {
 
+namespace {
+// static logger object
+Logger g_log("Unit");
+
+bool ParamPresent(const UnitParametersMap &params, UnitParams param) {
+  return params.find(param) != params.end();
+}
+
+bool ParamPresentAndSet(const UnitParametersMap *params, UnitParams param,
+                        double &var) {
+  auto it = params->find(param);
+  if (it != params->end()) {
+    var = it->second;
+    return true;
+  } else {
+    return false;
+  }
+}
+} // namespace
+
 /**
  * Default constructor
  * Gives the unit an empty UnitLabel
  */
-Unit::Unit()
-    : initialized(false), l1(0), l2(0), twoTheta(0), emode(0), efixed(0),
-      delta(0) {}
+Unit::Unit() : initialized(false), l1(0), emode(0) {}
 
 bool Unit::operator==(const Unit &u) const { return unitID() == u.unitID(); }
 
@@ -108,36 +128,43 @@ void Unit::addConversion(std::string to, const double &factor,
  *singleFromTof()
  *
  *  @param _l1 ::       The source-sample distance (in metres)
- *  @param _l2 ::       The sample-detector distance (in metres)
- *  @param _twoTheta :: The scattering angle (in radians)
  *  @param _emode ::    The energy mode (0=elastic, 1=direct geometry,
  *2=indirect geometry)
- *  @param _efixed ::   Value of fixed energy: EI (emode=1) or EF (emode=2) (in
- *meV)
- *  @param _delta ::    Not currently used
+ *  @param params ::  Map containing optional parameters eg
+ *                    The sample-detector distance (in metres)
+ *                    The scattering angle (in radians)
+ *                    Fixed energy: EI (emode=1) or EF (emode=2)(in meV)
+ *                    Delta (not currently used)
  */
-void Unit::initialize(const double &_l1, const double &_l2,
-                      const double &_twoTheta, const int &_emode,
-                      const double &_efixed, const double &_delta) {
+void Unit::initialize(const double &_l1, const int &_emode,
+                      const UnitParametersMap &params) {
   l1 = _l1;
-  l2 = _l2;
-  twoTheta = _twoTheta;
+  validateUnitParams(_emode, params);
   emode = _emode;
-  efixed = _efixed;
-  delta = _delta;
+  m_params = &params;
   initialized = true;
   this->init();
 }
 
+void Unit::validateUnitParams(const int, const UnitParametersMap &) {}
+
 //---------------------------------------------------------------------------------------
 /** Perform the conversion to TOF on a vector of data
  */
+
+void Unit::toTOF(
+    std::vector<double> &xdata, std::vector<double> &ydata, const double &_l1,
+    const int &_emode,
+    std::initializer_list<std::pair<const UnitParams, double>> params) {
+  UnitParametersMap paramsMap(params);
+  toTOF(xdata, ydata, _l1, _emode, paramsMap);
+}
+
 void Unit::toTOF(std::vector<double> &xdata, std::vector<double> &ydata,
-                 const double &_l1, const double &_l2, const double &_twoTheta,
-                 const int &_emode, const double &_efixed,
-                 const double &_delta) {
+                 const double &_l1, const int &_emode,
+                 const UnitParametersMap &params) {
   UNUSED_ARG(ydata);
-  this->initialize(_l1, _l2, _twoTheta, _emode, _efixed, _delta);
+  this->initialize(_l1, _emode, params);
   size_t numX = xdata.size();
   for (size_t i = 0; i < numX; i++)
     xdata[i] = this->singleToTOF(xdata[i]);
@@ -146,29 +173,32 @@ void Unit::toTOF(std::vector<double> &xdata, std::vector<double> &ydata,
 /** Convert a single value to TOF
 @param xvalue
 @param l1
-@param l2
-@param twoTheta
 @param emode
-@param efixed
-@param delta
+@param params (eg efixed or delta)
 */
 double Unit::convertSingleToTOF(const double xvalue, const double &l1,
-                                const double &l2, const double &twoTheta,
-                                const int &emode, const double &efixed,
-                                const double &delta) {
-  this->initialize(l1, l2, twoTheta, emode, efixed, delta);
+                                const int &emode,
+                                const UnitParametersMap &params) {
+  this->initialize(l1, emode, params);
   return this->singleToTOF(xvalue);
 }
 
 //---------------------------------------------------------------------------------------
 /** Perform the conversion to TOF on a vector of data
  */
+void Unit::fromTOF(
+    std::vector<double> &xdata, std::vector<double> &ydata, const double &_l1,
+    const int &_emode,
+    std::initializer_list<std::pair<const UnitParams, double>> params) {
+  UnitParametersMap paramsMap(params);
+  fromTOF(xdata, ydata, _l1, _emode, paramsMap);
+}
+
 void Unit::fromTOF(std::vector<double> &xdata, std::vector<double> &ydata,
-                   const double &_l1, const double &_l2,
-                   const double &_twoTheta, const int &_emode,
-                   const double &_efixed, const double &_delta) {
+                   const double &_l1, const int &_emode,
+                   const UnitParametersMap &params) {
   UNUSED_ARG(ydata);
-  this->initialize(_l1, _l2, _twoTheta, _emode, _efixed, _delta);
+  this->initialize(_l1, _emode, params);
   size_t numX = xdata.size();
   for (size_t i = 0; i < numX; i++)
     xdata[i] = this->singleFromTOF(xdata[i]);
@@ -177,17 +207,13 @@ void Unit::fromTOF(std::vector<double> &xdata, std::vector<double> &ydata,
 /** Convert a single value from TOF
 @param xvalue
 @param l1
-@param l2
-@param twoTheta
 @param emode
-@param efixed
-@param delta
+@param params (eg efixed or delta)
 */
 double Unit::convertSingleFromTOF(const double xvalue, const double &l1,
-                                  const double &l2, const double &twoTheta,
-                                  const int &emode, const double &efixed,
-                                  const double &delta) {
-  this->initialize(l1, l2, twoTheta, emode, efixed, delta);
+                                  const int &emode,
+                                  const UnitParametersMap &params) {
+  this->initialize(l1, emode, params);
   return this->singleFromTOF(xvalue);
 }
 
@@ -302,7 +328,7 @@ double TOF::conversionTOFMax() const { return DBL_MAX; }
 DECLARE_UNIT(Wavelength)
 
 Wavelength::Wavelength()
-    : Unit(), sfpTo(DBL_MIN), factorTo(DBL_MIN), sfpFrom(DBL_MIN),
+    : Unit(), efixed(0.), sfpTo(DBL_MIN), factorTo(DBL_MIN), sfpFrom(DBL_MIN),
       factorFrom(DBL_MIN), do_sfpFrom(false) {
   const double AngstromsSquared = 1e20;
   const double factor =
@@ -316,13 +342,31 @@ Wavelength::Wavelength()
 
 const UnitLabel Wavelength::label() const { return Symbol::Angstrom; }
 
+void Wavelength::validateUnitParams(const int emode,
+                                    const UnitParametersMap &params) {
+  if (!ParamPresent(params, UnitParams::l2)) {
+    throw std::runtime_error("An l2 value must be supplied in the extra "
+                             "parameters when initialising " +
+                             this->unitID() + " for conversion via TOF");
+  }
+  if ((emode != 0) && (!ParamPresent(params, UnitParams::efixed))) {
+    throw std::runtime_error("An efixed value must be supplied in the extra "
+                             "parameters when initialising " +
+                             this->unitID() + " for conversion via TOF");
+  }
+}
+
 void Wavelength::init() {
   // ------------ Factors to convert TO TOF ---------------------
+  double l2 = 0.0;
   double ltot = 0.0;
   double TOFisinMicroseconds = 1e6;
   double toAngstroms = 1e10;
   sfpTo = 0.0;
 
+  ParamPresentAndSet(m_params, UnitParams::efixed, efixed);
+  ParamPresentAndSet(m_params, UnitParams::l2, l2);
+
   if (emode == 1) {
     ltot = l2;
     sfpTo =
@@ -432,6 +476,8 @@ Energy::Energy() : Unit(), factorTo(DBL_MIN), factorFrom(DBL_MIN) {
 }
 
 void Energy::init() {
+  double l2 = 0.0;
+  ParamPresentAndSet(m_params, UnitParams::l2, l2);
   {
     const double TOFinMicroseconds = 1e6;
     factorTo =
@@ -490,7 +536,18 @@ Energy_inWavenumber::Energy_inWavenumber()
   addConversion("Momentum", 2 * M_PI / factor, 0.5);
 }
 
+void Energy_inWavenumber::validateUnitParams(const int,
+                                             const UnitParametersMap &params) {
+  if (!ParamPresent(params, UnitParams::l2)) {
+    throw std::runtime_error("An l2 value must be supplied in the extra "
+                             "parameters when initialising " +
+                             this->unitID() + " for conversion via TOF");
+  }
+}
+
 void Energy_inWavenumber::init() {
+  double l2 = 0.0;
+  ParamPresentAndSet(m_params, UnitParams::l2, l2);
   {
     const double TOFinMicroseconds = 1e6;
     factorTo = sqrt(PhysicalConstants::NeutronMass *
@@ -541,39 +598,188 @@ Unit *Energy_inWavenumber::clone() const {
  *
  * Conversion uses Bragg's Law: 2d sin(theta) = n * lambda
  */
-DECLARE_UNIT(dSpacing)
 
-const UnitLabel dSpacing::label() const { return Symbol::Angstrom; }
+const double CONSTANT = (PhysicalConstants::h * 1e10) /
+                        (2.0 * PhysicalConstants::NeutronMass * 1e6);
+
+/**
+ * Calculate and return conversion factor from tof to d-spacing.
+ * @param l1
+ * @param l2
+ * @param twoTheta scattering angle
+ * @param offset
+ * @return
+ */
+double tofToDSpacingFactor(const double l1, const double l2,
+                           const double twoTheta, const double offset) {
+  if (offset <=
+      -1.) // not physically possible, means result is negative d-spacing
+  {
+    std::stringstream msg;
+    msg << "Encountered offset of " << offset
+        << " which converts data to negative d-spacing\n";
+    throw std::logic_error(msg.str());
+  }
+
+  auto sinTheta = std::sin(twoTheta / 2);
+
+  const double numerator = (1.0 + offset);
+  sinTheta *= (l1 + l2);
+
+  return (numerator * CONSTANT) / sinTheta;
+}
+
+DECLARE_UNIT(dSpacing)
 
-dSpacing::dSpacing() : Unit(), factorTo(DBL_MIN), factorFrom(DBL_MIN) {
+dSpacing::dSpacing()
+    : Unit(), toDSpacingError(""), difa(0), difc(DBL_MIN), tzero(0) {
   const double factor = 2.0 * M_PI;
   addConversion("MomentumTransfer", factor, -1.0);
   addConversion("QSquared", (factor * factor), -2.0);
 }
 
+const UnitLabel dSpacing::label() const { return Symbol::Angstrom; }
+
+Unit *dSpacing::clone() const { return new dSpacing(*this); }
+
+void dSpacing::validateUnitParams(const int, const UnitParametersMap &params) {
+  double difc = 0.;
+  if (!ParamPresentAndSet(&params, UnitParams::difc, difc)) {
+    if (!ParamPresent(params, UnitParams::twoTheta) ||
+        (!ParamPresent(params, UnitParams::l2)))
+      throw std::runtime_error("A difc value or L2/two theta must be supplied "
+                               "in the extra parameters when initialising " +
+                               this->unitID() + " for conversion via TOF");
+  } else {
+    // check validations only applicable to fromTOF
+    toDSpacingError = "";
+    double difa = 0.;
+    ParamPresentAndSet(&params, UnitParams::difa, difa);
+    if ((difa == 0) && (difc == 0)) {
+      toDSpacingError = "Cannot convert to d spacing with DIFA=0 and DIFC=0";
+    };
+    // singleFromTOF currently assuming difc not negative
+    if (difc < 0.) {
+      toDSpacingError =
+          "A positive difc value must be supplied in the extra parameters when "
+          "initialising " +
+          this->unitID() + " for conversion via TOF";
+    }
+  }
+}
+
 void dSpacing::init() {
   // First the crux of the conversion
-  factorTo =
-      (2.0 * PhysicalConstants::NeutronMass * sin(twoTheta / 2.0) * (l1 + l2)) /
-      PhysicalConstants::h;
+  difa = 0.;
+  difc = 0.;
+  tzero = 0.;
+  ParamPresentAndSet(m_params, UnitParams::difa, difa);
+  ParamPresentAndSet(m_params, UnitParams::tzero, tzero);
+
+  if (!ParamPresentAndSet(m_params, UnitParams::difc, difc)) {
+    // also support inputs as L2, two theta
+    double l2;
+    if (ParamPresentAndSet(m_params, UnitParams::l2, l2)) {
+      double twoTheta;
+      if (ParamPresentAndSet(m_params, UnitParams::twoTheta, twoTheta)) {
+        if (difa != 0.) {
+          g_log.warning("Supplied difa ignored");
+          difa = 0.;
+        }
+        difc = 1. / tofToDSpacingFactor(l1, l2, twoTheta, 0.);
+        if (tzero != 0.) {
+          g_log.warning("Supplied tzero ignored");
+          tzero = 0.;
+        }
+      }
+    }
+  }
+}
 
-  // Now adjustments for the scale of units used
-  const double TOFisinMicroseconds = 1e6;
-  const double toAngstroms = 1e10;
-  factorTo *= TOFisinMicroseconds / toAngstroms;
-  factorFrom = factorTo;
-  if (factorFrom == 0.0)
-    factorFrom = DBL_MIN; // Protect against divide by zero
+double dSpacing::singleToTOF(const double x) const {
+  if (!isInitialized())
+    throw std::runtime_error("dSpacingBase::singleToTOF called before object "
+                             "has been initialized.");
+  return difa * x * x + difc * x + tzero;
 }
 
-double dSpacing::singleToTOF(const double x) const { return x * factorTo; }
 double dSpacing::singleFromTOF(const double tof) const {
-  return tof / factorFrom;
+  if (!isInitialized())
+    throw std::runtime_error("dSpacingBase::singleFromTOF called before object "
+                             "has been initialized.");
+  if (!toDSpacingError.empty())
+    throw std::runtime_error(toDSpacingError);
+  // handle special cases first...
+  if (tof == tzero) {
+    if (difa != 0)
+      return -difc / difa;
+  }
+  if ((difc * difc - 4 * difa * (tzero - tof)) < 0) {
+    throw std::runtime_error(
+        "Cannot convert to d spacing. Quadratic doesn't have real roots");
+  }
+  if ((difa > 0) && ((tzero - tof) > 0)) {
+    throw std::runtime_error("Cannot convert to d spacing. Quadratic doesn't "
+                             "have a positive root");
+  }
+  // and then solve quadratic using Muller formula
+  double sqrtTerm;
+  if (difa == 0) {
+    // avoid costly sqrt even though formula reduces to this
+    sqrtTerm = difc;
+  } else {
+    sqrtTerm = sqrt(difc * difc - 4 * difa * (tzero - tof));
+  }
+  if (sqrtTerm < difc)
+    return (tof - tzero) / (0.5 * (difc - sqrtTerm));
+  else
+    return (tof - tzero) / (0.5 * (difc + sqrtTerm));
+}
+double dSpacing::conversionTOFMin() const {
+  // quadratic only has a min if difa is positive
+  if (difa > 0) {
+    // min of the quadratic is at d=-difc/(2*difa)
+    return std::max(0., tzero - difc * difc / (4 * difa));
+  } else {
+    // no min so just pick value closest to zero that works
+    double TOFmin = singleToTOF(0.);
+    if (TOFmin < std::numeric_limits<double>::min()) {
+      TOFmin = 0.;
+    }
+    return TOFmin;
+  }
+}
+double dSpacing::conversionTOFMax() const {
+  // quadratic only has a max if difa is negative
+  if (difa < 0) {
+    return std::min(DBL_MAX, tzero - difc * difc / (4 * difa));
+  } else {
+    // no max so just pick value closest to DBL_MAX that works
+    double TOFmax = singleToTOF(DBL_MAX);
+    if (std::isinf(TOFmax)) {
+      TOFmax = DBL_MAX;
+    }
+    return TOFmax;
+  }
 }
-double dSpacing::conversionTOFMin() const { return 0; }
-double dSpacing::conversionTOFMax() const { return DBL_MAX / factorTo; }
 
-Unit *dSpacing::clone() const { return new dSpacing(*this); }
+double dSpacing::calcTofMin(const double difc, const double difa,
+                            const double tzero, const double tofmin) {
+  Kernel::UnitParametersMap params{{Kernel::UnitParams::difa, difa},
+                                   {Kernel::UnitParams::difc, difc},
+                                   {Kernel::UnitParams::tzero, tzero}};
+  initialize(-1., 0, params);
+  return std::max(conversionTOFMin(), tofmin);
+}
+
+double dSpacing::calcTofMax(const double difc, const double difa,
+                            const double tzero, const double tofmax) {
+  Kernel::UnitParametersMap params{{Kernel::UnitParams::difa, difa},
+                                   {Kernel::UnitParams::difc, difc},
+                                   {Kernel::UnitParams::tzero, tzero}};
+  initialize(-1, 0, params);
+  return std::min(conversionTOFMax(), tofmax);
+}
 
 // ==================================================================================================
 /* D-SPACING Perpendicular
@@ -590,7 +796,26 @@ const UnitLabel dSpacingPerpendicular::label() const {
 dSpacingPerpendicular::dSpacingPerpendicular()
     : Unit(), factorTo(DBL_MIN), factorFrom(DBL_MIN) {}
 
+void dSpacingPerpendicular::validateUnitParams(
+    const int, const UnitParametersMap &params) {
+  if (!ParamPresent(params, UnitParams::l2)) {
+    throw std::runtime_error(
+        "A l2 value must be supplied in the extra parameters when "
+        "initialising " +
+        this->unitID() + " for conversion via TOF");
+  }
+  if (!ParamPresent(params, UnitParams::twoTheta)) {
+    throw std::runtime_error(
+        "A two theta value must be supplied in the extra parameters when "
+        "initialising " +
+        this->unitID() + " for conversion via TOF");
+  }
+}
+
 void dSpacingPerpendicular::init() {
+  double l2 = 0.0;
+  ParamPresentAndSet(m_params, UnitParams::l2, l2);
+  ParamPresentAndSet(m_params, UnitParams::twoTheta, twoTheta);
   factorTo =
       (PhysicalConstants::NeutronMass * (l1 + l2)) / PhysicalConstants::h;
 
@@ -642,47 +867,50 @@ const UnitLabel MomentumTransfer::label() const {
   return Symbol::InverseAngstrom;
 }
 
-MomentumTransfer::MomentumTransfer()
-    : Unit(), factorTo(DBL_MIN), factorFrom(DBL_MIN) {
+MomentumTransfer::MomentumTransfer() : Unit() {
   addConversion("QSquared", 1.0, 2.0);
   const double factor = 2.0 * M_PI;
   addConversion("dSpacing", factor, -1.0);
 }
 
+void MomentumTransfer::validateUnitParams(const int,
+                                          const UnitParametersMap &params) {
+  double difc = 0.;
+  if (!ParamPresentAndSet(&params, UnitParams::difc, difc)) {
+    if (!ParamPresent(params, UnitParams::twoTheta) ||
+        (!ParamPresent(params, UnitParams::l2)))
+      throw std::runtime_error("A difc value or L2/two theta must be supplied "
+                               "in the extra parameters when initialising " +
+                               this->unitID() + " for conversion via TOF");
+  };
+}
+
 void MomentumTransfer::init() {
   // First the crux of the conversion
-  factorTo = (4.0 * M_PI * PhysicalConstants::NeutronMass * (l1 + l2) *
-              sin(twoTheta / 2.0)) /
-             PhysicalConstants::h;
-  // Now adjustments for the scale of units used
-  const double TOFisinMicroseconds = 1e6;
-  const double toAngstroms = 1e10;
-  factorTo *= TOFisinMicroseconds / toAngstroms;
-  // First the crux of the conversion
-  factorFrom = (4.0 * M_PI * PhysicalConstants::NeutronMass * (l1 + l2) *
-                sin(twoTheta / 2.0)) /
-               PhysicalConstants::h;
-
-  // Now adjustments for the scale of units used
-  factorFrom *= TOFisinMicroseconds / toAngstroms;
+  difc = 0.;
+
+  if (!ParamPresentAndSet(m_params, UnitParams::difc, difc)) {
+    // also support inputs as L2, two theta
+    double l2;
+    if (ParamPresentAndSet(m_params, UnitParams::l2, l2)) {
+      double twoTheta;
+      if (ParamPresentAndSet(m_params, UnitParams::twoTheta, twoTheta)) {
+        difc = 1. / tofToDSpacingFactor(l1, l2, twoTheta, 0.);
+      }
+    }
+  }
 }
 
 double MomentumTransfer::singleToTOF(const double x) const {
-  double temp = x;
-  if (temp == 0.0)
-    temp = DBL_MIN; // Protect against divide by zero
-  return factorTo / temp;
+  return 2. * M_PI * difc / x;
 }
 //
 double MomentumTransfer::singleFromTOF(const double tof) const {
-  double temp = tof;
-  if (temp == 0.0)
-    temp = DBL_MIN; // Protect against divide by zero
-  return factorFrom / temp;
+  return 2. * M_PI * difc / tof;
 }
 
 double MomentumTransfer::conversionTOFMin() const {
-  return factorFrom / DBL_MAX;
+  return 2. * M_PI * difc / DBL_MAX;
 }
 double MomentumTransfer::conversionTOFMax() const { return DBL_MAX; }
 
@@ -696,55 +924,27 @@ DECLARE_UNIT(QSquared)
 
 const UnitLabel QSquared::label() const { return Symbol::InverseAngstromSq; }
 
-QSquared::QSquared() : Unit(), factorTo(DBL_MIN), factorFrom(DBL_MIN) {
+QSquared::QSquared() : MomentumTransfer() {
   addConversion("MomentumTransfer", 1.0, 0.5);
   const double factor = 2.0 * M_PI;
   addConversion("dSpacing", factor, -0.5);
 }
 
-void QSquared::init() {
-  // First the crux of the conversion
-  factorTo = (4.0 * M_PI * PhysicalConstants::NeutronMass * (l1 + l2) *
-              sin(twoTheta / 2.0)) /
-             PhysicalConstants::h;
-  // Now adjustments for the scale of units used
-  const double TOFisinMicroseconds = 1e6;
-  const double toAngstroms = 1e10;
-  factorTo *= TOFisinMicroseconds / toAngstroms;
-
-  // First the crux of the conversion
-  factorFrom = (4.0 * M_PI * PhysicalConstants::NeutronMass * (l1 + l2) *
-                sin(twoTheta / 2.0)) /
-               PhysicalConstants::h;
-  // Now adjustments for the scale of units used
-  factorFrom *= TOFisinMicroseconds / toAngstroms;
-  factorFrom = factorFrom * factorFrom;
-}
-
 double QSquared::singleToTOF(const double x) const {
-  double temp = x;
-  if (temp == 0.0)
-    temp = DBL_MIN; // Protect against divide by zero
-  return factorTo / sqrt(temp);
+  return MomentumTransfer::singleToTOF(sqrt(x));
 }
 double QSquared::singleFromTOF(const double tof) const {
-  double temp = tof;
-  if (temp == 0.0)
-    temp = DBL_MIN; // Protect against divide by zero
-  return factorFrom / (temp * temp);
+  return pow(MomentumTransfer::singleFromTOF(tof), 2);
 }
 
 double QSquared::conversionTOFMin() const {
-  if (factorTo > 0)
-    return factorTo / sqrt(DBL_MAX);
-  else
-    return -sqrt(DBL_MAX);
+  return 2 * M_PI * difc / sqrt(DBL_MAX);
 }
 double QSquared::conversionTOFMax() const {
-  if (factorTo > 0)
-    return sqrt(DBL_MAX);
-  else
-    return factorTo / sqrt(DBL_MAX);
+  double tofmax = 2 * M_PI * difc / sqrt(DBL_MIN);
+  if (std::isinf(tofmax))
+    tofmax = DBL_MAX;
+  return tofmax;
 }
 
 Unit *QSquared::clone() const { return new QSquared(*this); }
@@ -764,11 +964,38 @@ DeltaE::DeltaE()
   addConversion("DeltaE_inFrequency", PhysicalConstants::meVtoFrequency, 1.);
 }
 
-void DeltaE::init() {
-  // Efixed must be set to something
-  if (efixed == 0.0)
+void DeltaE::validateUnitParams(const int emode,
+                                const UnitParametersMap &params) {
+  if (emode != 1 && emode != 2) {
     throw std::invalid_argument(
-        "efixed must be set for energy transfer calculation");
+        "emode must be equal to 1 or 2 for energy transfer calculation");
+  }
+  // Efixed must be set to something
+  double efixed;
+  if (!ParamPresentAndSet(&params, UnitParams::efixed, efixed)) {
+    if (emode == 1) { // direct, efixed=ei
+      throw std::invalid_argument(
+          "efixed must be set for energy transfer calculation");
+    } else {
+      throw std::runtime_error(
+          "efixed must be set for energy transfer calculation");
+    }
+  }
+  if (efixed <= 0) {
+    throw std::runtime_error("efixed must be greater than zero");
+  }
+  if (!ParamPresent(params, UnitParams::l2)) {
+    throw std::runtime_error(
+        "A l2 value must be supplied in the extra parameters when "
+        "initialising " +
+        this->unitID() + " for conversion via TOF");
+  }
+}
+
+void DeltaE::init() {
+  double l2 = 0.0;
+  ParamPresentAndSet(m_params, UnitParams::l2, l2);
+  ParamPresentAndSet(m_params, UnitParams::efixed, efixed);
   const double TOFinMicroseconds = 1e6;
   factorTo =
       sqrt(PhysicalConstants::NeutronMass / (2.0 * PhysicalConstants::meV)) *
@@ -781,9 +1008,6 @@ void DeltaE::init() {
     // t_other is t2
     t_other = (factorTo * l2) / sqrt(efixed);
     factorTo *= l1;
-  } else {
-    throw std::invalid_argument(
-        "emode must be equal to 1 or 2 for energy transfer calculation");
   }
 
   //------------ from conversion ------------------
@@ -876,8 +1100,8 @@ Unit *DeltaE::clone() const { return new DeltaE(*this); }
 /* Energy Transfer in units of wavenumber
  * =====================================================================================================
  *
- * This is identical to the above (Energy Transfer in meV) with one division by
- *meVtoWavenumber.
+ * This is identical to the above (Energy Transfer in meV) with one division
+ *by meVtoWavenumber.
  */
 DECLARE_UNIT(DeltaE_inWavenumber)
 
@@ -948,7 +1172,7 @@ DECLARE_UNIT(Momentum)
 const UnitLabel Momentum::label() const { return Symbol::InverseAngstrom; }
 
 Momentum::Momentum()
-    : Unit(), sfpTo(DBL_MIN), factorTo(DBL_MIN), sfpFrom(DBL_MIN),
+    : Unit(), efixed(0.), sfpTo(DBL_MIN), factorTo(DBL_MIN), sfpFrom(DBL_MIN),
       factorFrom(DBL_MIN), do_sfpFrom(false) {
 
   const double AngstromsSquared = 1e20;
@@ -964,13 +1188,31 @@ Momentum::Momentum()
   //
 }
 
+void Momentum::validateUnitParams(const int emode,
+                                  const UnitParametersMap &params) {
+  if (!ParamPresent(params, UnitParams::l2)) {
+    throw std::runtime_error(
+        "An l2 value must be supplied in the extra parameters when "
+        "initialising momentum for conversion via TOF");
+  }
+  if ((emode != 0) && (!ParamPresent(params, UnitParams::efixed))) {
+    throw std::runtime_error(
+        "An efixed value must be supplied in the extra parameters when "
+        "initialising momentum for conversion via TOF");
+  }
+}
+
 void Momentum::init() {
   // ------------ Factors to convert TO TOF ---------------------
+  double l2 = 0.0;
   double ltot = 0.0;
   double TOFisinMicroseconds = 1e6;
   double toAngstroms = 1e10;
   sfpTo = 0.0;
 
+  ParamPresentAndSet(m_params, UnitParams::l2, l2);
+  ParamPresentAndSet(m_params, UnitParams::efixed, efixed);
+
   if (emode == 1) {
     ltot = l2;
     sfpTo =
@@ -1065,6 +1307,7 @@ const UnitLabel SpinEchoLength::label() const { return Symbol::Nanometre; }
 SpinEchoLength::SpinEchoLength() : Wavelength() {}
 
 void SpinEchoLength::init() {
+  ParamPresentAndSet(m_params, UnitParams::efixed, efixed);
   // Efixed must be set to something
   if (efixed == 0.0)
     throw std::invalid_argument(
@@ -1113,9 +1356,10 @@ DECLARE_UNIT(SpinEchoTime)
 
 const UnitLabel SpinEchoTime::label() const { return Symbol::Nanosecond; }
 
-SpinEchoTime::SpinEchoTime() : Wavelength() {}
+SpinEchoTime::SpinEchoTime() : Wavelength(), efixed(0.) {}
 
 void SpinEchoTime::init() {
+  ParamPresentAndSet(m_params, UnitParams::efixed, efixed);
   // Efixed must be set to something
   if (efixed == 0.0)
     throw std::invalid_argument(
diff --git a/Framework/Kernel/src/UnitConversion.cpp b/Framework/Kernel/src/UnitConversion.cpp
index 3f9a4694fab..727fd58efa4 100644
--- a/Framework/Kernel/src/UnitConversion.cpp
+++ b/Framework/Kernel/src/UnitConversion.cpp
@@ -32,8 +32,23 @@ double UnitConversion::run(const std::string &src, const std::string &dest,
                            const DeltaEMode::Type emode, const double efixed) {
   Unit_sptr srcUnit = UnitFactory::Instance().create(src);
   Unit_sptr destUnit = UnitFactory::Instance().create(dest);
-  return UnitConversion::run(*srcUnit, *destUnit, srcValue, l1, l2, theta,
-                             emode, efixed);
+  if ((srcUnit->unitID() == "dSpacing") || (destUnit->unitID() == "dSpacing")) {
+    throw std::runtime_error(
+        "This signature is deprecated for d Spacing unit conversions");
+  }
+  UnitParametersMap params{{UnitParams::l2, l2},
+                           {UnitParams::twoTheta, theta},
+                           {UnitParams::efixed, efixed}};
+  return UnitConversion::run(*srcUnit, *destUnit, srcValue, l1, emode, params);
+} // namespace Kernel
+
+double UnitConversion::run(const std::string &src, const std::string &dest,
+                           const double srcValue, const double l1,
+                           const DeltaEMode::Type emode,
+                           const UnitParametersMap &params) {
+  Unit_sptr srcUnit = UnitFactory::Instance().create(src);
+  Unit_sptr destUnit = UnitFactory::Instance().create(dest);
+  return UnitConversion::run(*srcUnit, *destUnit, srcValue, l1, emode, params);
 }
 
 /**
@@ -42,22 +57,22 @@ double UnitConversion::run(const std::string &src, const std::string &dest,
  * @param destUnit :: The destination unit
  * @param srcValue :: The value to convert
  * @param l1 ::       The source-sample distance (in metres)
- * @param l2 ::       The sample-detector distance (in metres)
- * @param theta :: The scattering angle (in radians)
  * @param emode ::    The energy mode enumeration
- * @param efixed ::   Value of fixed energy: EI (emode=1) or EF (emode=2) (in
- * meV)
+ * @param params ::  Map containing optional parameters eg
+ *                   The sample-detector distance (in metres)
+ *                   The scattering angle (in radians)
+ *                   Fixed energy: EI (emode=1) or EF (emode=2)(in meV)
+ *                   Delta (not currently used)
  * @return The value converted to the destination unit
  */
 double UnitConversion::run(Unit &srcUnit, Unit &destUnit, const double srcValue,
-                           const double l1, const double l2, const double theta,
-                           const DeltaEMode::Type emode, const double efixed) {
+                           const double l1, const DeltaEMode::Type emode,
+                           const UnitParametersMap &params) {
   double factor(0.0), power(0.0);
   if (srcUnit.quickConversion(destUnit, factor, power)) {
     return convertQuickly(srcValue, factor, power);
   } else {
-    return convertViaTOF(srcUnit, destUnit, srcValue, l1, l2, theta, emode,
-                         efixed);
+    return convertViaTOF(srcUnit, destUnit, srcValue, l1, emode, params);
   }
 }
 
@@ -83,18 +98,18 @@ double UnitConversion::convertQuickly(const double srcValue,
  * @param destUnit :: The destination unit
  * @param srcValue :: The value to convert
  * @param l1 ::       The source-sample distance (in metres)
- * @param l2 ::       The sample-detector distance (in metres)
- * @param theta :: The scattering angle (in radians)
  * @param emode ::    The energy mode enumeration
- * @param efixed ::   Value of fixed energy: EI (emode=1) or EF (emode=2) (in
- * meV)
+ * @param params ::  Map containing optional parameters eg
+ *                   The sample-detector distance (in metres)
+ *                   The scattering angle (in radians)
+ *                   Fixed energy: EI (emode=1) or EF (emode=2)(in meV)
+ *                   Delta (not currently used)
  * @return The value converted to the destination unit
  */
 double UnitConversion::convertViaTOF(Unit &srcUnit, Unit &destUnit,
                                      const double srcValue, const double l1,
-                                     const double l2, const double theta,
                                      const DeltaEMode::Type emode,
-                                     const double efixed) {
+                                     const UnitParametersMap &params) {
   // Translate the emode to the int formulation
   int emodeAsInt(0);
   switch (emode) {
@@ -113,11 +128,9 @@ double UnitConversion::convertViaTOF(Unit &srcUnit, Unit &destUnit,
         std::to_string(emode));
   };
 
-  const double unused(0.0);
-  const double tof = srcUnit.convertSingleToTOF(srcValue, l1, l2, theta,
-                                                emodeAsInt, efixed, unused);
-  return destUnit.convertSingleFromTOF(tof, l1, l2, theta, emodeAsInt, efixed,
-                                       unused);
+  const double tof =
+      srcUnit.convertSingleToTOF(srcValue, l1, emodeAsInt, params);
+  return destUnit.convertSingleFromTOF(tof, l1, emodeAsInt, params);
 }
 
 /**
diff --git a/Framework/Kernel/test/UnitTest.h b/Framework/Kernel/test/UnitTest.h
index a0bf000969f..1adabc83dca 100644
--- a/Framework/Kernel/test/UnitTest.h
+++ b/Framework/Kernel/test/UnitTest.h
@@ -100,6 +100,17 @@ std::string convert_units_check_range(const Unit &aUnit,
   return error_mess;
 }
 
+namespace {                // anonymous
+const double DIFC = 2100.; // sensible value
+const double TZERO = 10.;
+// intentionally goofy - reduces tzero by 1
+const double DIFA1 = .25 * DIFC * DIFC;
+// intentionally goofy - reduces tzero by .01
+const double DIFA2 = 25 * DIFC * DIFC;
+// intentionally goofy
+const double DIFA3 = -.25 * DIFC * DIFC;
+} // namespace
+
 class UnitTest : public CxxTest::TestSuite {
 
   class UnitTester : public Unit {
@@ -253,7 +264,11 @@ public:
 
   void test_copy_constructor_on_concrete_type() {
     Units::TOF first;
-    first.initialize(1.0, 1.0, 1.0, 2, 1.0, 1.0);
+    first.initialize(1.0, 2,
+                     {{UnitParams::l2, 1.0},
+                      {UnitParams::twoTheta, 1.0},
+                      {UnitParams::efixed, 1.0},
+                      {UnitParams::delta, 1.0}});
     Units::TOF second(first);
     TS_ASSERT_EQUALS(first.isInitialized(), second.isInitialized());
     TS_ASSERT_EQUALS(first.unitID(), second.unitID())
@@ -264,7 +279,11 @@ public:
 
   void test_copy_assignment_operator_on_concrete_type() {
     Units::TOF first;
-    first.initialize(1.0, 1.0, 1.0, 2, 1.0, 1.0);
+    first.initialize(1.0, 2,
+                     {{UnitParams::l2, 1.0},
+                      {UnitParams::twoTheta, 1.0},
+                      {UnitParams::efixed, 1.0},
+                      {UnitParams::delta, 1.0}});
     Units::TOF second;
     second = first;
     TS_ASSERT_EQUALS(first.isInitialized(), second.isInitialized());
@@ -291,7 +310,7 @@ public:
     std::vector<double> x(20, 9.9), y(20, 8.8);
     std::vector<double> xx = x;
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(tof.toTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    TS_ASSERT_THROWS_NOTHING(tof.toTOF(x, y, 1.0, 1, {}))
     // Check vectors are unchanged
     TS_ASSERT(xx == x)
     TS_ASSERT(yy == y)
@@ -301,7 +320,7 @@ public:
     std::vector<double> x(20, 9.9), y(20, 8.8);
     std::vector<double> xx = x;
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(tof.fromTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    TS_ASSERT_THROWS_NOTHING(tof.fromTOF(x, y, 1.0, 1, {}))
     // Check vectors are unchanged
     TS_ASSERT(xx == x)
     TS_ASSERT(yy == y)
@@ -342,23 +361,28 @@ public:
   void testWavelength_toTOF() {
     std::vector<double> x(1, 1.5), y(1, 1.5);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(lambda.toTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    TS_ASSERT_THROWS_NOTHING(lambda.toTOF(
+        x, y, 1.0, 1, {{UnitParams::l2, 1.0}, {UnitParams::efixed, 1.0}}))
     TS_ASSERT_DELTA(x[0], 2665.4390, 0.0001) //  758.3352
     TS_ASSERT(yy == y)
 
-    TS_ASSERT_DELTA(lambda.convertSingleToTOF(1.5, 1.0, 1.0, 1.0, 1, 1.0, 1.0),
-                    2665.4390, 0.0001);
+    TS_ASSERT_DELTA(
+        lambda.convertSingleToTOF(
+            1.5, 1.0, 1, {{UnitParams::l2, 1.0}, {UnitParams::efixed, 1.0}}),
+        2665.4390, 0.0001);
   }
 
   void testWavelength_fromTOF() {
     std::vector<double> x(1, 1000.5), y(1, 1.5);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(lambda.fromTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    TS_ASSERT_THROWS_NOTHING(lambda.fromTOF(
+        x, y, 1.0, 1, {{UnitParams::l2, 1.0}, {UnitParams::efixed, 1.0}}))
     TS_ASSERT_DELTA(x[0], -5.0865, 0.0001) // 1.979006
     TS_ASSERT(yy == y)
 
     TS_ASSERT_DELTA(
-        lambda.convertSingleFromTOF(1000.5, 1.0, 1.0, 1.0, 1, 1.0, 1.0),
+        lambda.convertSingleFromTOF(
+            1000.5, 1.0, 1, {{UnitParams::l2, 1.0}, {UnitParams::efixed, 1.0}}),
         -5.0865, 0.0001);
   }
 
@@ -369,8 +393,9 @@ public:
     double input = 1.1;
     double result = factor * std::pow(input, power);
     std::vector<double> x(1, input);
-    lambda.toTOF(x, x, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
-    energy.fromTOF(x, x, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
+    lambda.toTOF(x, x, 99.0, 99,
+                 {{UnitParams::l2, 99.0}, {UnitParams::efixed, 1.0}});
+    energy.fromTOF(x, x, 99.0, 99, {{UnitParams::l2, 99.0}});
     TS_ASSERT_DELTA(x[0], result, 1.0e-10)
 
     TS_ASSERT(lambda.quickConversion(energyk, factor, power))
@@ -378,8 +403,10 @@ public:
     TS_ASSERT_EQUALS(result2 / result,
                      Mantid::PhysicalConstants::meVtoWavenumber)
     std::vector<double> x2(1, input);
-    lambda.toTOF(x2, x2, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
-    energyk.fromTOF(x2, x2, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
+    lambda.toTOF(x2, x2, 99.0, 99,
+                 {{UnitParams::l2, 99.0}, {UnitParams::efixed, 99.0}});
+
+    energyk.fromTOF(x2, x2, 99.0, 99, {{UnitParams::l2, 99.0}});
     TS_ASSERT_DELTA(x2[0], result2, 1.0e-10)
   }
 
@@ -417,7 +444,8 @@ public:
   void testEnergy_toTOF() {
     std::vector<double> x(1, 4.0), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(energy.toTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    TS_ASSERT_THROWS_NOTHING(
+        energy.toTOF(x, y, 1.0, 1, {{UnitParams::l2, 1.0}}))
     TS_ASSERT_DELTA(x[0], 2286.271, 0.001)
     TS_ASSERT(yy == y)
   }
@@ -425,7 +453,8 @@ public:
   void testEnergy_fromTOF() {
     std::vector<double> x(1, 4.0), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(energy.fromTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    TS_ASSERT_THROWS_NOTHING(
+        energy.fromTOF(x, y, 1.0, 1, {{UnitParams::l2, 1.0}}))
     TS_ASSERT_DELTA(x[0], 1306759.0, 1.0)
     TS_ASSERT(yy == y)
   }
@@ -438,15 +467,16 @@ public:
     double result = factor * std::pow(input, power);
     TS_ASSERT_EQUALS(result / input, Mantid::PhysicalConstants::meVtoWavenumber)
     std::vector<double> x(1, input);
-    energy.toTOF(x, x, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
-    energyk.fromTOF(x, x, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
+    energy.toTOF(x, x, 99.0, 99, {{UnitParams::l2, 99.0}});
+    energyk.fromTOF(x, x, 99.0, 99, {{UnitParams::l2, 99.0}});
     TS_ASSERT_DELTA(x[0], result, 1.0e-12)
 
     TS_ASSERT(energy.quickConversion(lambda, factor, power))
     result = factor * std::pow(input, power);
     std::vector<double> x2(1, input);
-    energy.toTOF(x2, x2, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
-    lambda.fromTOF(x2, x2, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
+    energy.toTOF(x2, x2, 99.0, 99, {{UnitParams::l2, 99.0}});
+    lambda.fromTOF(x2, x2, 99.0, 99,
+                   {{UnitParams::l2, 99.0}, {UnitParams::efixed, 99.0}});
     TS_ASSERT_DELTA(x2[0], result, 1.0e-15)
   }
   void testEnergyRange() {
@@ -492,7 +522,8 @@ public:
   void testEnergy_inWavenumber_toTOF() {
     std::vector<double> x(1, 4.0), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(energyk.toTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    TS_ASSERT_THROWS_NOTHING(
+        energyk.toTOF(x, y, 1.0, 1, {{UnitParams::l2, 1.0}}))
     TS_ASSERT_DELTA(x[0], 6492.989, 0.001)
     TS_ASSERT(yy == y)
   }
@@ -500,7 +531,8 @@ public:
   void testEnergy_inWavenumber_fromTOF() {
     std::vector<double> x(1, 4.0), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(energyk.fromTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    TS_ASSERT_THROWS_NOTHING(
+        energyk.fromTOF(x, y, 1.0, 1, {{UnitParams::l2, 1.0}}))
     TS_ASSERT_DELTA(x[0], 10539725, 1.0)
     TS_ASSERT(yy == y)
   }
@@ -513,15 +545,16 @@ public:
     double result = factor * std::pow(input, power);
     TS_ASSERT_EQUALS(input / result, Mantid::PhysicalConstants::meVtoWavenumber)
     std::vector<double> x(1, input);
-    energyk.toTOF(x, x, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
-    energy.fromTOF(x, x, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
+    energyk.toTOF(x, x, 99.0, 99, {{UnitParams::l2, 99.0}});
+    energy.fromTOF(x, x, 99.0, 99, {{UnitParams::l2, 99.0}});
     TS_ASSERT_DELTA(x[0], result, 1.0e-14)
 
     TS_ASSERT(energyk.quickConversion(lambda, factor, power))
     result = factor * std::pow(input, power);
     std::vector<double> x2(1, input);
-    energyk.toTOF(x2, x2, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
-    lambda.fromTOF(x2, x2, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
+    energyk.toTOF(x2, x2, 99.0, 99, {{UnitParams::l2, 99.0}});
+    lambda.fromTOF(x2, x2, 99.0, 99,
+                   {{UnitParams::l2, 99.0}, {UnitParams::efixed, 99.0}});
     TS_ASSERT_DELTA(x2[0], result, 1.0e-15)
   }
 
@@ -547,19 +580,62 @@ public:
   void testdSpacing_toTOF() {
     std::vector<double> x(1, 1.0), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(d.toTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    double difc = 2.0 * Mantid::PhysicalConstants::NeutronMass * sin(0.5) *
+                  (1.0 + 1.0) * 1e-4 / Mantid::PhysicalConstants::h;
+    TS_ASSERT_THROWS_NOTHING(d.toTOF(x, y, 1.0, 1, {{UnitParams::difc, difc}}))
+    TS_ASSERT_DELTA(x[0], 484.7537, 0.0001)
+    TS_ASSERT(yy == y)
+  }
+
+  void testdSpacing_toTOFWithL2TwoTheta() {
+    std::vector<double> x(1, 1.0), y(1, 1.0);
+    std::vector<double> yy = y;
+    TS_ASSERT_THROWS_NOTHING(d.toTOF(
+        x, y, 1.0, 1, {{UnitParams::l2, 1.0}, {UnitParams::twoTheta, 1.0}}))
     TS_ASSERT_DELTA(x[0], 484.7537, 0.0001)
     TS_ASSERT(yy == y)
   }
 
+  void testdSpacing_toTOFWithDIFATZERO() {
+    std::vector<double> x(1, 2.0), y(1, 1.0);
+    std::vector<double> yy = y;
+    TS_ASSERT_THROWS_NOTHING(d.toTOF(x, y, 1.0, 1,
+                                     {{UnitParams::difc, 3.0},
+                                      {UnitParams::difa, 2.0},
+                                      {UnitParams::tzero, 1.0}}))
+    TS_ASSERT_DELTA(x[0], 6.0 + 8.0 + 1.0, 0.0001)
+    TS_ASSERT(yy == y)
+  }
+
   void testdSpacing_fromTOF() {
     std::vector<double> x(1, 1001.1), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(d.fromTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    double difc = 2.0 * Mantid::PhysicalConstants::NeutronMass * sin(0.5) *
+                  (1.0 + 1.0) * 1e-4 / Mantid::PhysicalConstants::h;
+    TS_ASSERT_THROWS_NOTHING(
+        d.fromTOF(x, y, 1.0, 1, {{UnitParams::difc, difc}}))
     TS_ASSERT_DELTA(x[0], 2.065172, 0.000001)
     TS_ASSERT(yy == y)
   }
 
+  void testdSpacing_fromTOFWithDIFATZERO() {
+    std::vector<double> x(1, 2.0), y(1, 1.0);
+    std::vector<double> yy = y;
+    TS_ASSERT_THROWS_NOTHING(d.fromTOF(x, y, 1.0, 1,
+                                       {{UnitParams::difc, 2.0},
+                                        {UnitParams::difa, 3.0},
+                                        {UnitParams::tzero, 1.0}}))
+    TS_ASSERT_DELTA(x[0], 1.0 / 3.0, 0.0001)
+    TS_ASSERT(yy == y)
+    x[0] = 1.0;
+    TS_ASSERT_THROWS_NOTHING(d.fromTOF(x, y, 1.0, 1,
+                                       {{UnitParams::difc, 3.0},
+                                        {UnitParams::difa, -2.0},
+                                        {UnitParams::tzero, 1.0}}))
+    TS_ASSERT_DELTA(x[0], 1.5, 0.0001)
+    TS_ASSERT(yy == y)
+  }
+
   void testdSpacing_quickConversions() {
     // Test it gives the same answer as going 'the long way'
     // To MomentumTransfer
@@ -568,8 +644,10 @@ public:
     double input = 1.1;
     double result = factor * std::pow(input, power);
     std::vector<double> x(1, input);
-    d.toTOF(x, x, 99.0, 99.0, 1.0, 0, 99.0, 99.0);
-    q.fromTOF(x, x, 99.0, 99.0, 1.0, 0, 99.0, 99.0);
+    double difc = 2.0 * Mantid::PhysicalConstants::NeutronMass * sin(0.5) *
+                  (99.0 + 99.0) * 1e-4 / Mantid::PhysicalConstants::h;
+    d.toTOF(x, x, 99.0, 0, {{UnitParams::difc, difc}});
+    q.fromTOF(x, x, 99.0, 0, {{UnitParams::difc, difc}});
     TS_ASSERT_DELTA(x[0], result, 1.0e-12)
 
     // To QSquared
@@ -577,13 +655,17 @@ public:
     input = 1.1;
     result = factor * std::pow(input, power);
     x[0] = input;
-    d.toTOF(x, x, 99.0, 99.0, 1.0, 0, 99.0, 99.0);
-    q2.fromTOF(x, x, 99.0, 99.0, 1.0, 0, 99.0, 99.0);
+    d.toTOF(x, x, 99.0, 0, {{UnitParams::difc, difc}});
+    q2.fromTOF(x, x, 99.0, 0, {{UnitParams::difc, difc}});
     TS_ASSERT_DELTA(x[0], result, 1.0e-12)
   }
   void testdSpacingRange() {
     std::vector<double> sample, rezult;
 
+    double difc = 2.0 * Mantid::PhysicalConstants::NeutronMass *
+                  sin(0.5 * M_PI / 180) * (99.0 + 99.0) * 1e-4 /
+                  Mantid::PhysicalConstants::h;
+    d.initialize(99.0, 0, {{UnitParams::difc, difc}});
     std::string err_mess = convert_units_check_range(d, sample, rezult);
     TSM_ASSERT(" ERROR:" + err_mess, err_mess.size() == 0);
 
@@ -600,6 +682,52 @@ public:
     }
   }
 
+  void test_calcTofMin() {
+    const double TMIN = 300.;
+
+    // just difc
+    TS_ASSERT_EQUALS(d.calcTofMin(DIFC, 0., 0.), 0.);
+    TS_ASSERT_EQUALS(d.calcTofMin(DIFC, 0., 0., TMIN), TMIN);
+    // difc + tzero
+    TS_ASSERT_EQUALS(d.calcTofMin(DIFC, 0., TZERO, 0.), TZERO);
+    TS_ASSERT_EQUALS(d.calcTofMin(DIFC, 0., TZERO, TMIN), TMIN);
+
+    // difc + difa + tzero
+    TS_ASSERT_EQUALS(d.calcTofMin(DIFC, DIFA1, 0., 0.), 0.);
+    TS_ASSERT_EQUALS(d.calcTofMin(DIFC, DIFA1, 0., TMIN), TMIN);
+    TS_ASSERT_EQUALS(d.calcTofMin(DIFC, DIFA1, TZERO, 0.), TZERO - 1.);
+    TS_ASSERT_EQUALS(d.calcTofMin(DIFC, DIFA1, TZERO, TMIN), TMIN);
+
+    TS_ASSERT_EQUALS(d.calcTofMin(DIFC, DIFA2, 0., 0.), 0.);
+    TS_ASSERT_EQUALS(d.calcTofMin(DIFC, DIFA2, 0., TMIN), TMIN);
+    TS_ASSERT_EQUALS(d.calcTofMin(DIFC, DIFA2, TZERO, 0.), TZERO - .01);
+    TS_ASSERT_EQUALS(d.calcTofMin(DIFC, DIFA2, TZERO, TMIN), TMIN);
+  }
+
+  void test_calcTofMax() {
+    const double TMAX = 16666.7;
+    const double TSUPERMAX = std::numeric_limits<double>::max();
+
+    // just difc
+    TS_ASSERT_EQUALS(d.calcTofMax(DIFC, 0., 0., TMAX), TMAX);
+    TS_ASSERT_EQUALS(d.calcTofMax(DIFC, 0., 0., TSUPERMAX), TSUPERMAX);
+    // difc + tzero
+    TS_ASSERT_EQUALS(d.calcTofMax(DIFC, 0., TZERO, TMAX), TMAX);
+    TS_ASSERT_EQUALS(d.calcTofMax(DIFC, 0., TZERO, TSUPERMAX), TSUPERMAX);
+
+    // difc + difa + tzero
+    TS_ASSERT_EQUALS(d.calcTofMax(DIFC, DIFA1, 0., TMAX), TMAX);
+    TS_ASSERT_EQUALS(d.calcTofMax(DIFC, DIFA1, 0., TSUPERMAX), TSUPERMAX);
+    TS_ASSERT_EQUALS(d.calcTofMax(DIFC, DIFA1, TZERO, TMAX), TMAX);
+    TS_ASSERT_EQUALS(d.calcTofMax(DIFC, DIFA1, TZERO, TSUPERMAX), TSUPERMAX);
+
+    TS_ASSERT_DELTA(d.calcTofMax(DIFC, DIFA3, 0., TMAX), 1., 1E-10);
+    TS_ASSERT_DELTA(d.calcTofMax(DIFC, DIFA3, 0., TSUPERMAX), 1., 1E-10);
+    TS_ASSERT_DELTA(d.calcTofMax(DIFC, DIFA3, TZERO, TMAX), TZERO + 1., 1E-10);
+    TS_ASSERT_DELTA(d.calcTofMax(DIFC, DIFA3, TZERO, TSUPERMAX), TZERO + 1.,
+                    1E-10);
+  }
+
   //----------------------------------------------------------------------
   // d-SpacingPerpebdicular tests
   //----------------------------------------------------------------------
@@ -626,7 +754,8 @@ public:
   void testdSpacingPerpendicular_toTOF() {
     std::vector<double> x(1, 1.0), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(dp.toTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    TS_ASSERT_THROWS_NOTHING(dp.toTOF(
+        x, y, 1.0, 1, {{UnitParams::l2, 1.0}, {UnitParams::twoTheta, 1.0}}))
     TS_ASSERT_DELTA(x[0], 434.5529, 0.0001)
     TS_ASSERT(yy == y)
   }
@@ -634,7 +763,8 @@ public:
   void testdSpacingPerpendicular_fromTOF() {
     std::vector<double> x(1, 1001.1), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(dp.fromTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    TS_ASSERT_THROWS_NOTHING(dp.fromTOF(
+        x, y, 1.0, 1, {{UnitParams::l2, 1.0}, {UnitParams::twoTheta, 1.0}}))
     TS_ASSERT_DELTA(x[0], 2.045075, 0.000001)
     TS_ASSERT(yy == y)
   }
@@ -684,7 +814,9 @@ public:
   void testQTransfer_toTOF() {
     std::vector<double> x(1, 1.1), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(q.toTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    double difc = 2.0 * Mantid::PhysicalConstants::NeutronMass * sin(0.5) *
+                  (1.0 + 1.0) * 1e-4 / Mantid::PhysicalConstants::h;
+    TS_ASSERT_THROWS_NOTHING(q.toTOF(x, y, 1.0, 1, {{UnitParams::difc, difc}}))
     TS_ASSERT_DELTA(x[0], 2768.9067, 0.0001)
     TS_ASSERT(yy == y)
   }
@@ -692,7 +824,10 @@ public:
   void testQTransfer_fromTOF() {
     std::vector<double> x(1, 1.1), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(q.fromTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    double difc = 2.0 * Mantid::PhysicalConstants::NeutronMass * sin(0.5) *
+                  (1.0 + 1.0) * 1e-4 / Mantid::PhysicalConstants::h;
+    TS_ASSERT_THROWS_NOTHING(
+        q.fromTOF(x, y, 1.0, 1, {{UnitParams::difc, difc}}))
     TS_ASSERT_DELTA(x[0], 2768.9067, 0.0001)
     TS_ASSERT(yy == y)
   }
@@ -705,8 +840,10 @@ public:
     double input = 1.1;
     double result = factor * std::pow(input, power);
     std::vector<double> x(1, input);
-    q.toTOF(x, x, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
-    q2.fromTOF(x, x, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
+    double difc = 2.0 * Mantid::PhysicalConstants::NeutronMass * sin(1.0 / 2) *
+                  (99.0 + 99.0) * 1e-4 / Mantid::PhysicalConstants::h;
+    q.toTOF(x, x, 99.0, 99, {{UnitParams::difc, difc}});
+    q2.fromTOF(x, x, 99.0, 99, {{UnitParams::difc, difc}});
     TS_ASSERT_DELTA(x[0], result, 1.0e-30)
 
     // To dSpacing
@@ -714,8 +851,8 @@ public:
     input = 1.1;
     result = factor * std::pow(input, power);
     x[0] = input;
-    q.toTOF(x, x, 99.0, 99.0, 1.0, 99, 99.0, 99.0);
-    d.fromTOF(x, x, 99.0, 99.0, 1.0, 99, 99.0, 99.0);
+    q.toTOF(x, x, 99.0, 99, {{UnitParams::difc, difc}});
+    d.fromTOF(x, x, 99.0, 99, {{UnitParams::difc, difc}});
     TS_ASSERT_DELTA(x[0], result, 1.0e-12)
   }
   void testMomentumTransferRange() {
@@ -759,7 +896,9 @@ public:
   void testQ2_toTOF() {
     std::vector<double> x(1, 4.0), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(q2.toTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    double difc = 2.0 * Mantid::PhysicalConstants::NeutronMass * sin(1.0 / 2) *
+                  (1.0 + 1.0) * 1e-4 / Mantid::PhysicalConstants::h;
+    TS_ASSERT_THROWS_NOTHING(q2.toTOF(x, y, 1.0, 1, {{UnitParams::difc, difc}}))
     TS_ASSERT_DELTA(x[0], 1522.899, 0.001)
     TS_ASSERT(yy == y)
   }
@@ -767,7 +906,10 @@ public:
   void testQ2_fromTOF() {
     std::vector<double> x(1, 200.0), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(q2.fromTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    double difc = 2.0 * Mantid::PhysicalConstants::NeutronMass * sin(1.0 / 2) *
+                  (1.0 + 1.0) * 1e-4 / Mantid::PhysicalConstants::h;
+    TS_ASSERT_THROWS_NOTHING(
+        q2.fromTOF(x, y, 1.0, 1, {{UnitParams::difc, difc}}))
     TS_ASSERT_DELTA(x[0], 231.9220, 0.0001)
     TS_ASSERT(yy == y)
   }
@@ -780,8 +922,10 @@ public:
     double input = 1.1;
     double result = factor * std::pow(input, power);
     std::vector<double> x(1, input);
-    q2.toTOF(x, x, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
-    q.fromTOF(x, x, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
+    double difc = 2.0 * Mantid::PhysicalConstants::NeutronMass * sin(1.0 / 2) *
+                  (99.0 + 99.0) * 1e-4 / Mantid::PhysicalConstants::h;
+    q2.toTOF(x, x, 99.0, 99, {{UnitParams::difc, difc}});
+    q.fromTOF(x, x, 99.0, 99, {{UnitParams::difc, difc}});
     TS_ASSERT_DELTA(x[0], result, 1.0e-30)
 
     // To dSpacing
@@ -789,16 +933,19 @@ public:
     input = 1.1;
     result = factor * std::pow(input, power);
     x[0] = input;
-    q2.toTOF(x, x, 99.0, 99.0, 1.0, 99, 99.0, 99.0);
-    d.fromTOF(x, x, 99.0, 99.0, 1.0, 99, 99.0, 99.0);
+    q2.toTOF(x, x, 99.0, 99, {{UnitParams::difc, difc}});
+    d.fromTOF(x, x, 99.0, 99, {{UnitParams::difc, difc}});
     TS_ASSERT_DELTA(x[0], result, 1.0e-15)
   }
   void testQ2Range() {
     std::vector<double> sample, rezult;
 
-    q2.initialize(1.1, 1.1, 99.0, 0, 99.0, 0);
-    std::string err_mess =
-        convert_units_check_range(q2, sample, rezult, -DBL_EPSILON);
+    double difc = 2.0 * Mantid::PhysicalConstants::NeutronMass * sin(1.0 / 2) *
+                  (1.1 + 1.1) * 1e-4 / Mantid::PhysicalConstants::h;
+    q2.initialize(1.1, 0,
+                  {{UnitParams::difc, difc}, {UnitParams::efixed, 99.0}});
+
+    std::string err_mess = convert_units_check_range(q2, sample, rezult);
     TSM_ASSERT(" ERROR:" + err_mess, err_mess.size() == 0);
 
     for (size_t i = 0; i < sample.size(); i++) {
@@ -838,40 +985,49 @@ public:
   void testDeltaE_toTOF() {
     std::vector<double> x(1, 1.1), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(dE.toTOF(x, y, 1.5, 2.5, 0.0, 1, 4.0, 0.0))
+    TS_ASSERT_THROWS_NOTHING(dE.toTOF(
+        x, y, 1.5, 1, {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}))
     TS_ASSERT_DELTA(x[0], 5071.066, 0.001)
     TS_ASSERT(yy == y)
 
     x[0] = 1.1;
-    TS_ASSERT_THROWS_NOTHING(dE.toTOF(x, y, 1.5, 2.5, 0.0, 2, 4.0, 0.0))
+    TS_ASSERT_THROWS_NOTHING(dE.toTOF(
+        x, y, 1.5, 2, {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}))
     TS_ASSERT_DELTA(x[0], 4376.406, 0.001)
     TS_ASSERT(yy == y)
 
     // emode = 0
-    TS_ASSERT_THROWS(dE.toTOF(x, y, 1.5, 2.5, 0.0, 0, 4.0, 0.0),
-                     const std::invalid_argument &)
+    TS_ASSERT_THROWS(
+        dE.toTOF(x, y, 1.5, 0,
+                 {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}),
+        const std::invalid_argument &)
   }
 
   void testDeltaE_fromTOF() {
     std::vector<double> x(1, 2001.0), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(dE.fromTOF(x, y, 1.5, 2.5, 0.0, 1, 4.0, 0.0))
+    TS_ASSERT_THROWS_NOTHING(dE.fromTOF(
+        x, y, 1.5, 1, {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}))
     TS_ASSERT_DELTA(x[0], -394.5692, 0.0001)
     TS_ASSERT(yy == y)
 
     x[0] = 3001.0;
-    TS_ASSERT_THROWS_NOTHING(dE.fromTOF(x, y, 1.5, 2.5, 0.0, 2, 4.0, 0.0))
+    TS_ASSERT_THROWS_NOTHING(dE.fromTOF(
+        x, y, 1.5, 2, {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}))
     TS_ASSERT_DELTA(x[0], 569.8397, 0.0001)
     TS_ASSERT(yy == y)
 
     // emode = 0
-    TS_ASSERT_THROWS(dE.fromTOF(x, y, 1.5, 2.5, 0.0, 0, 4.0, 0.0),
-                     const std::invalid_argument &)
+    TS_ASSERT_THROWS(
+        dE.fromTOF(x, y, 1.5, 0,
+                   {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}),
+        const std::invalid_argument &)
   }
   void testDERange() {
     std::vector<double> sample, rezult;
     // Direct
-    dE.initialize(2001.0, 1.0, 1.5, 1, 10., 0.0);
+    dE.initialize(2001.0, 1,
+                  {{UnitParams::l2, 1.0}, {UnitParams::efixed, 10.0}});
 
     std::string err_mess =
         convert_units_check_range(dE, sample, rezult, DBL_EPSILON);
@@ -891,7 +1047,8 @@ public:
         sample[3], rezult[3], 10 * FLT_EPSILON);
 
     // Indirect
-    dE.initialize(2001.0, 1.0, 1.5, 2, 10., 0.0);
+    dE.initialize(2001.0, 2,
+                  {{UnitParams::l2, 1.0}, {UnitParams::efixed, 10.0}});
 
     err_mess = convert_units_check_range(dE, sample, rezult);
     TSM_ASSERT(" ERROR:" + err_mess, err_mess.size() == 0);
@@ -936,40 +1093,49 @@ public:
   void testDeltaEk_toTOF() {
     std::vector<double> x(1, 1.1), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(dEk.toTOF(x, y, 1.5, 2.5, 0.0, 1, 4.0, 0.0))
+    TS_ASSERT_THROWS_NOTHING(dEk.toTOF(
+        x, y, 1.5, 1, {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}))
     TS_ASSERT_DELTA(x[0], 4622.5452, 0.01)
     TS_ASSERT(yy == y)
 
     x[0] = 1.1;
-    TS_ASSERT_THROWS_NOTHING(dEk.toTOF(x, y, 1.5, 2.5, 0.0, 2, 4.0, 0.0))
+    TS_ASSERT_THROWS_NOTHING(dEk.toTOF(
+        x, y, 1.5, 2, {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}))
     TS_ASSERT_DELTA(x[0], 4544.0378, 0.001)
     TS_ASSERT(yy == y)
 
     // emode = 0
-    TS_ASSERT_THROWS(dEk.toTOF(x, y, 1.5, 2.5, 0.0, 0, 4.0, 0.0),
-                     const std::invalid_argument &)
+    TS_ASSERT_THROWS(
+        dEk.toTOF(x, y, 1.5, 0,
+                  {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}),
+        const std::invalid_argument &)
   }
 
   void testDeltaEk_fromTOF() {
     std::vector<double> x(1, 2001.0), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(dEk.fromTOF(x, y, 1.5, 2.5, 0.0, 1, 4.0, 0.0))
+    TS_ASSERT_THROWS_NOTHING(dEk.fromTOF(
+        x, y, 1.5, 1, {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}))
     TS_ASSERT_DELTA(x[0], -3182.416, 0.001)
     TS_ASSERT(yy == y)
 
     x[0] = 3001.0;
-    TS_ASSERT_THROWS_NOTHING(dEk.fromTOF(x, y, 1.5, 2.5, 0.0, 2, 4.0, 0.0))
+    TS_ASSERT_THROWS_NOTHING(dEk.fromTOF(
+        x, y, 1.5, 2, {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}))
     TS_ASSERT_DELTA(x[0], 4596.068, 0.001)
     TS_ASSERT(yy == y)
 
     // emode = 0
-    TS_ASSERT_THROWS(dEk.fromTOF(x, y, 1.5, 2.5, 0.0, 0, 4.0, 0.0),
-                     const std::invalid_argument &)
+    TS_ASSERT_THROWS(
+        dEk.fromTOF(x, y, 1.5, 0,
+                    {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}),
+        const std::invalid_argument &)
   }
   void testDE_kRange() {
     std::vector<double> sample, rezult;
     // Direct
-    dEk.initialize(2001.0, 1.0, 1.5, 1, 10., 0.0);
+    dEk.initialize(2001.0, 1,
+                   {{UnitParams::l2, 1.0}, {UnitParams::efixed, 10.0}});
 
     std::string err_mess = convert_units_check_range(dEk, sample, rezult);
     TSM_ASSERT(" ERROR:" + err_mess, err_mess.size() == 0);
@@ -988,7 +1154,8 @@ public:
         sample[3], rezult[3], 10 * FLT_EPSILON);
 
     // Indirect
-    dEk.initialize(2001.0, 1.0, 1.5, 2, 10., 0.0);
+    dEk.initialize(2001.0, 2,
+                   {{UnitParams::l2, 1.0}, {UnitParams::efixed, 10.0}});
 
     err_mess = convert_units_check_range(dEk, sample, rezult);
     TSM_ASSERT(" ERROR:" + err_mess, err_mess.size() == 0);
@@ -1034,41 +1201,50 @@ public:
     std::vector<double> x(1, 0.26597881882),
         y(1, 1.0); // 1.1meV = h*0.26597881882Ghz
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(dEf.toTOF(x, y, 1.5, 2.5, 0.0, 1, 4.0, 0.0))
+    TS_ASSERT_THROWS_NOTHING(dEf.toTOF(
+        x, y, 1.5, 1, {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}))
     TS_ASSERT_DELTA(x[0], 5071.066, 0.001)
     TS_ASSERT(yy == y)
 
     x[0] = 0.26597881882;
-    TS_ASSERT_THROWS_NOTHING(dEf.toTOF(x, y, 1.5, 2.5, 0.0, 2, 4.0, 0.0))
+    TS_ASSERT_THROWS_NOTHING(dEf.toTOF(
+        x, y, 1.5, 2, {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}))
     TS_ASSERT_DELTA(x[0], 4376.406, 0.001)
     TS_ASSERT(yy == y)
 
     // emode = 0
-    TS_ASSERT_THROWS(dEf.toTOF(x, y, 1.5, 2.5, 0.0, 0, 4.0, 0.0),
-                     const std::invalid_argument &)
+    TS_ASSERT_THROWS(
+        dEf.toTOF(x, y, 1.5, 0,
+                  {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}),
+        const std::invalid_argument &)
   }
 
   void testDeltaEf_fromTOF() {
     std::vector<double> x(1, 2001.0), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(dEf.fromTOF(x, y, 1.5, 2.5, 0.0, 1, 4.0, 0.0))
+    TS_ASSERT_THROWS_NOTHING(dEf.fromTOF(
+        x, y, 1.5, 1, {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}))
     TS_ASSERT_DELTA(x[0], -95.4064, 0.0001)
     TS_ASSERT(yy == y)
 
     x[0] = 3001.0;
-    TS_ASSERT_THROWS_NOTHING(dEf.fromTOF(x, y, 1.5, 2.5, 0.0, 2, 4.0, 0.0))
+    TS_ASSERT_THROWS_NOTHING(dEf.fromTOF(
+        x, y, 1.5, 2, {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}))
     TS_ASSERT_DELTA(x[0], 137.7866, 0.0001)
     TS_ASSERT(yy == y)
 
     // emode = 0
-    TS_ASSERT_THROWS(dEf.fromTOF(x, y, 1.5, 2.5, 0.0, 0, 4.0, 0.0),
-                     const std::invalid_argument &)
+    TS_ASSERT_THROWS(
+        dEf.fromTOF(x, y, 1.5, 0,
+                    {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}),
+        const std::invalid_argument &)
   }
 
   void testDE_fRange() {
     std::vector<double> sample, rezult;
     // Direct
-    dEf.initialize(2001.0, 1.0, 1.5, 1, 10., 0.0);
+    dEf.initialize(2001.0, 1,
+                   {{UnitParams::l2, 1.0}, {UnitParams::efixed, 10.0}});
 
     std::string err_mess =
         convert_units_check_range(dEf, sample, rezult, DBL_EPSILON);
@@ -1088,7 +1264,8 @@ public:
         sample[3], rezult[3], 10 * FLT_EPSILON);
 
     // Indirect
-    dEf.initialize(2001.0, 1.0, 1.5, 2, 10., 0.0);
+    dEf.initialize(2001.0, 2,
+                   {{UnitParams::l2, 1.0}, {UnitParams::efixed, 10.0}});
 
     err_mess = convert_units_check_range(dEf, sample, rezult);
     TSM_ASSERT(" ERROR:" + err_mess, err_mess.size() == 0);
@@ -1129,7 +1306,8 @@ public:
   void testMomentum_toTOF() {
     std::vector<double> x(1, 2 * M_PI / 1.5), y(1, 1.5);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(k_i.toTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    TS_ASSERT_THROWS_NOTHING(k_i.toTOF(
+        x, y, 1.0, 1, {{UnitParams::l2, 1.0}, {UnitParams::efixed, 1.0}}))
     // TS_ASSERT_DELTA( x[0], 2665.4390, 0.0001 ) // -- wavelength to TOF;
     TS_ASSERT_DELTA(x[0], 2665.4390, 0.0001) //
     TS_ASSERT(yy == y)
@@ -1138,7 +1316,8 @@ public:
   void testMomentum_fromTOF() {
     std::vector<double> x(1, 1000.5), y(1, 1.5);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(k_i.fromTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    TS_ASSERT_THROWS_NOTHING(k_i.fromTOF(
+        x, y, 1.0, 1, {{UnitParams::l2, 1.0}, {UnitParams::efixed, 1.0}}))
     //    TS_ASSERT_DELTA( x[0], -5.0865, 0.0001 ) // wavelength from TOF
     TS_ASSERT_DELTA(x[0], 2 * M_PI / (-5.0865), 0.0001) // 1.979006
     TS_ASSERT(yy == y)
@@ -1151,8 +1330,10 @@ public:
     double input = 1.1;
     double result = factor * std::pow(input, power);
     std::vector<double> x(1, input);
-    k_i.toTOF(x, x, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
-    energy.fromTOF(x, x, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
+    k_i.toTOF(x, x, 99.0, 99,
+              {{UnitParams::l2, 99.0}, {UnitParams::efixed, 99.0}});
+    energy.fromTOF(x, x, 99.0, 99,
+                   {{UnitParams::l2, 99.0}, {UnitParams::efixed, 99.0}});
     TS_ASSERT_DELTA(x[0], result, 1.0e-10)
 
     TS_ASSERT(k_i.quickConversion(energyk, factor, power))
@@ -1160,8 +1341,10 @@ public:
     TS_ASSERT_EQUALS(result2 / result,
                      Mantid::PhysicalConstants::meVtoWavenumber)
     std::vector<double> x2(1, input);
-    k_i.toTOF(x2, x2, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
-    energyk.fromTOF(x2, x2, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
+    k_i.toTOF(x2, x2, 99.0, 99,
+              {{UnitParams::l2, 99.0}, {UnitParams::efixed, 99.0}});
+    energyk.fromTOF(x2, x2, 99.0, 99,
+                    {{UnitParams::l2, 99.0}, {UnitParams::efixed, 99.0}});
     TS_ASSERT_DELTA(x2[0], result2, 1.0e-10);
 
     TS_ASSERT(k_i.quickConversion(lambda, factor, power));
@@ -1173,7 +1356,7 @@ public:
   }
   void testK_iRange() {
     std::vector<double> sample, rezult;
-    k_i.initialize(1.1, 1.1, 99.0, 0, 99.0, 99);
+    k_i.initialize(1.1, 0, {{UnitParams::l2, 1.1}});
 
     std::string err_mess =
         convert_units_check_range(k_i, sample, rezult, DBL_EPSILON);
@@ -1193,7 +1376,8 @@ public:
       }
     }
 
-    k_i.initialize(10000, 11, 99.0, 2, 99.0, 99);
+    k_i.initialize(10000, 2,
+                   {{UnitParams::l2, 11}, {UnitParams::efixed, 99.0}});
 
     err_mess = convert_units_check_range(k_i, sample, rezult, DBL_EPSILON);
     TSM_ASSERT(" ERROR:" + err_mess, err_mess.size() == 0);
@@ -1212,7 +1396,7 @@ public:
       }
     }
 
-    k_i.initialize(1, 1.1, 99.0, 1, 99.0, 99);
+    k_i.initialize(1, 1, {{UnitParams::l2, 1.1}, {UnitParams::efixed, 99.0}});
 
     err_mess = convert_units_check_range(k_i, sample, rezult, DBL_EPSILON);
     TSM_ASSERT(" ERROR:" + err_mess, err_mess.size() == 0);
@@ -1258,30 +1442,36 @@ public:
   void testSpinEchoLength_toTOF() {
     std::vector<double> x(1, 4.5), y(1, 1.5);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(delta.toTOF(x, y, 1.0, 1.0, 1.0, 0, 2.0, 1.0))
+    TS_ASSERT_THROWS_NOTHING(delta.toTOF(
+        x, y, 1.0, 0, {{UnitParams::l2, 1.0}, {UnitParams::efixed, 2.0}}))
     TS_ASSERT_DELTA(x[0], 758.3352, 0.0001)
     TS_ASSERT(yy == y)
 
-    TS_ASSERT_DELTA(delta.convertSingleToTOF(4.5, 1.0, 1.0, 1.0, 0, 2.0, 1.0),
-                    758.3352, 0.0001);
+    TS_ASSERT_DELTA(
+        delta.convertSingleToTOF(
+            4.5, 1.0, 0, {{UnitParams::l2, 1.0}, {UnitParams::efixed, 2.0}}),
+        758.3352, 0.0001);
   }
 
   void testSpinEchoLength_fromTOF() {
     std::vector<double> x(1, 1000.5), y(1, 1.5);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(delta.fromTOF(x, y, 1.0, 1.0, 1.0, 0, 2.0, 1.0))
+    TS_ASSERT_THROWS_NOTHING(delta.fromTOF(
+        x, y, 1.0, 0, {{UnitParams::l2, 1.0}, {UnitParams::efixed, 2.0}}))
     TS_ASSERT_DELTA(x[0], 7.8329, 0.0001)
     TS_ASSERT(yy == y)
 
     TS_ASSERT_DELTA(
-        delta.convertSingleFromTOF(1000.5, 1.0, 1.0, 1.0, 0, 2.0, 1.0), 7.8329,
-        0.0001);
+        delta.convertSingleFromTOF(
+            1000.5, 1.0, 0, {{UnitParams::l2, 1.0}, {UnitParams::efixed, 2.0}}),
+        7.8329, 0.0001);
   }
 
   void testSpinEchoLength_invalidfromTOF() {
     std::vector<double> x(1, 1000.5), y(1, 1.5);
     // emode must = 0
-    TS_ASSERT_THROWS_ANYTHING(delta.fromTOF(x, y, 1.0, 1.0, 1.0, 1, 2.0, 1.0))
+    TS_ASSERT_THROWS_ANYTHING(delta.fromTOF(
+        x, y, 1.0, 1, {{UnitParams::l2, 1.0}, {UnitParams::efixed, 2.0}}))
   }
 
   void testSpinEchoLength_quickConversions() {
@@ -1292,7 +1482,8 @@ public:
   }
   void testSpinEchoRange() {
     std::vector<double> sample, rezult;
-    delta.initialize(10, 1.1, 99.0, 0, 99.0, 99);
+    delta.initialize(10, 0,
+                     {{UnitParams::l2, 1.1}, {UnitParams::efixed, 99.0}});
 
     std::string err_mess = convert_units_check_range(delta, sample, rezult);
     TSM_ASSERT(" ERROR:" + err_mess, err_mess.size() == 0);
@@ -1336,30 +1527,36 @@ public:
   void testSpinEchoTime_toTOF() {
     std::vector<double> x(1, 4.5), y(1, 1.5);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(tau.toTOF(x, y, 1.0, 1.0, 1.0, 0, 2.0, 1.0))
+    TS_ASSERT_THROWS_NOTHING(tau.toTOF(
+        x, y, 1.0, 0, {{UnitParams::l2, 1.0}, {UnitParams::efixed, 2.0}}))
     TS_ASSERT_DELTA(x[0], 662.4668, 0.0001)
     TS_ASSERT(yy == y)
 
-    TS_ASSERT_DELTA(tau.convertSingleToTOF(4.5, 1.0, 1.0, 1.0, 0, 2.0, 1.0),
-                    662.4668, 0.0001);
+    TS_ASSERT_DELTA(
+        tau.convertSingleToTOF(
+            4.5, 1.0, 0, {{UnitParams::l2, 1.0}, {UnitParams::efixed, 2.0}}),
+        662.4668, 0.0001);
   }
 
   void testSpinEchoTime_fromTOF() {
     std::vector<double> x(1, 1000.5), y(1, 1.5);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(tau.fromTOF(x, y, 1.0, 1.0, 1.0, 0, 2.0, 1.0))
+    TS_ASSERT_THROWS_NOTHING(tau.fromTOF(
+        x, y, 1.0, 0, {{UnitParams::l2, 1.0}, {UnitParams::efixed, 2.0}}))
     TS_ASSERT_DELTA(x[0], 15.5014, 0.0001)
     TS_ASSERT(yy == y)
 
     TS_ASSERT_DELTA(
-        tau.convertSingleFromTOF(1000.5, 1.0, 1.0, 1.0, 0, 2.0, 1.0), 15.5014,
-        0.0001);
+        tau.convertSingleFromTOF(
+            1000.5, 1.0, 0, {{UnitParams::l2, 1.0}, {UnitParams::efixed, 2.0}}),
+        15.5014, 0.0001);
   }
 
   void testSpinEchoTime_invalidfromTOF() {
     std::vector<double> x(1, 1000.5), y(1, 1.5);
     // emode must = 0
-    TS_ASSERT_THROWS_ANYTHING(tau.fromTOF(x, y, 1.0, 1.0, 1.0, 1, 2.0, 1.0))
+    TS_ASSERT_THROWS_ANYTHING(tau.fromTOF(
+        x, y, 1.0, 1, {{UnitParams::l2, 1.0}, {UnitParams::efixed, 2.0}}))
   }
 
   void testSpinEchoTime_quickConversions() {
@@ -1370,7 +1567,7 @@ public:
   }
   void testSpinEchoTimeRange() {
     std::vector<double> sample, rezult;
-    tau.initialize(100, 11, 1.0, 0, 1.0, 1);
+    tau.initialize(100, 0, {{UnitParams::l2, 11}});
 
     std::string err_mess =
         convert_units_check_range(tau, sample, rezult, DBL_EPSILON);
diff --git a/Framework/MDAlgorithms/inc/MantidMDAlgorithms/UnitsConversionHelper.h b/Framework/MDAlgorithms/inc/MantidMDAlgorithms/UnitsConversionHelper.h
index 9418deb7f6e..75aab2e980a 100644
--- a/Framework/MDAlgorithms/inc/MantidMDAlgorithms/UnitsConversionHelper.h
+++ b/Framework/MDAlgorithms/inc/MantidMDAlgorithms/UnitsConversionHelper.h
@@ -48,12 +48,15 @@ class DLLExport UnitsConversionHelper {
   //  these variables needed and used for conversion through TOF
   int m_Emode;
 
-  double m_L1, m_Efix, m_TwoTheta, m_L2;
+  double m_L1, m_Efix, m_TwoTheta, m_L2, m_DIFA, m_DIFC, m_TZERO;
   std::vector<double> const *m_pTwoThetas;
   std::vector<double> const *m_pL2s;
   // pointer to detector specific input energy (eFixed) defined for indirect
   // instruments;
   float *m_pEfixedArray;
+  std::vector<double> const *m_pDIFAs;
+  std::vector<double> const *m_pDIFCs;
+  std::vector<double> const *m_pTZEROs;
 
 public:
   UnitsConversionHelper();
diff --git a/Framework/MDAlgorithms/src/PreprocessDetectorsToMD.cpp b/Framework/MDAlgorithms/src/PreprocessDetectorsToMD.cpp
index 34e03a3dd87..2a8740cd2eb 100644
--- a/Framework/MDAlgorithms/src/PreprocessDetectorsToMD.cpp
+++ b/Framework/MDAlgorithms/src/PreprocessDetectorsToMD.cpp
@@ -161,6 +161,9 @@ PreprocessDetectorsToMD::createTableWorkspace(
   m_getEFixed = this->getProperty("GetEFixed");
   if (m_getEFixed)
     targWS->addColumn("float", "eFixed");
+  targWS->addColumn("double", "DIFA");
+  targWS->addColumn("double", "DIFC");
+  targWS->addColumn("double", "TZERO");
 
   // will see about that
   // sin^2(Theta)
@@ -220,6 +223,9 @@ void PreprocessDetectorsToMD::processDetectorsPositions(
   auto &TwoTheta = targWS->getColVector<double>("TwoTheta");
   auto &Azimuthal = targWS->getColVector<double>("Azimuthal");
   auto &detDir = targWS->getColVector<Kernel::V3D>("DetDirections");
+  auto &DIFA = targWS->getColVector<double>("DIFA");
+  auto &DIFC = targWS->getColVector<double>("DIFC");
+  auto &TZERO = targWS->getColVector<double>("TZERO");
 
   // Efixed; do we need one and does one exist?
   auto Efi = targWS->getLogs()->getPropertyValueAsType<double>("Ei");
@@ -272,6 +278,13 @@ void PreprocessDetectorsToMD::processDetectorsPositions(
     TwoTheta[liveDetectorsCount] = polar;
     Azimuthal[liveDetectorsCount] = azim;
 
+    std::vector<int> warningDets;
+    auto diffConsts = spectrumInfo.diffractometerConstants(i, warningDets);
+    // map will create an entry with zero value if not present already
+    DIFA[liveDetectorsCount] = diffConsts[Kernel::UnitParams::difa];
+    DIFC[liveDetectorsCount] = diffConsts[Kernel::UnitParams::difc];
+    TZERO[liveDetectorsCount] = diffConsts[Kernel::UnitParams::tzero];
+
     double sPhi = sin(polar);
     double ez = cos(polar);
     double ex = sPhi * cos(azim);
diff --git a/Framework/MDAlgorithms/src/UnitsConversionHelper.cpp b/Framework/MDAlgorithms/src/UnitsConversionHelper.cpp
index a327a4b6223..186b8f5d0c9 100644
--- a/Framework/MDAlgorithms/src/UnitsConversionHelper.cpp
+++ b/Framework/MDAlgorithms/src/UnitsConversionHelper.cpp
@@ -10,6 +10,8 @@
 #include "MantidKernel/UnitFactory.h"
 #include <cmath>
 
+using Mantid::Kernel::UnitParams;
+
 namespace Mantid {
 namespace MDAlgorithms {
 
@@ -265,6 +267,10 @@ void UnitsConversionHelper::initialize(
   if (m_Emode == static_cast<int>(Kernel::DeltaEMode::Indirect))
     m_pEfixedArray = DetWS->getColDataArray<float>("eFixed");
 
+  m_pDIFAs = &(DetWS->getColVector<double>("DIFA"));
+  m_pDIFCs = &(DetWS->getColVector<double>("DIFC"));
+  m_pTZEROs = &(DetWS->getColVector<double>("TZERO"));
+
   // set up conversion to working state -- in some tests it can be used straight
   // from the beginning.
   m_TwoTheta = (*m_pTwoThetas)[0];
@@ -272,14 +278,29 @@ void UnitsConversionHelper::initialize(
   double Efix = m_Efix;
   if (m_pEfixedArray)
     Efix = static_cast<double>(*(m_pEfixedArray + 0));
-
-  m_TargetUnit->initialize(m_L1, m_L2, m_TwoTheta, m_Emode, Efix, 0.);
+  m_DIFA = (*m_pDIFAs)[0];
+  m_DIFC = (*m_pDIFCs)[0];
+  m_TZERO = (*m_pTZEROs)[0];
+
+  m_TargetUnit->initialize(m_L1, m_Emode,
+                           {{UnitParams::l2, m_L2},
+                            {UnitParams::twoTheta, m_TwoTheta},
+                            {UnitParams::efixed, Efix},
+                            {UnitParams::difa, m_DIFA},
+                            {UnitParams::difc, m_DIFC},
+                            {UnitParams::tzero, m_TZERO}});
   if (m_SourceWSUnit) {
-    m_SourceWSUnit->initialize(m_L1, m_L2, m_TwoTheta, m_Emode, Efix, 0.);
+    m_SourceWSUnit->initialize(m_L1, m_Emode,
+                               {{UnitParams::l2, m_L2},
+                                {UnitParams::twoTheta, m_TwoTheta},
+                                {UnitParams::efixed, Efix},
+                                {UnitParams::difa, m_DIFA},
+                                {UnitParams::difc, m_DIFC},
+                                {UnitParams::tzero, m_TZERO}});
   }
 }
-/** Method updates unit conversion given the index of detector parameters in the
- * array of detectors */
+/** Method updates unit conversion given the index of detector parameters in
+ * the array of detectors */
 void UnitsConversionHelper::updateConversion(size_t i) {
   switch (m_UnitCnvrsn) {
   case (CnvrtToMD::ConvertNo):
@@ -287,33 +308,47 @@ void UnitsConversionHelper::updateConversion(size_t i) {
   case (CnvrtToMD::ConvertFast):
     return;
   case (CnvrtToMD::ConvertFromTOF): {
-    double delta(std::numeric_limits<double>::quiet_NaN());
     m_TwoTheta = (*m_pTwoThetas)[i];
     m_L2 = (*m_pL2s)[i];
     double Efix = m_Efix;
     if (m_pEfixedArray)
       Efix = static_cast<double>(*(m_pEfixedArray + i));
-
-    m_TargetUnit->initialize(m_L1, m_L2, m_TwoTheta, m_Emode, Efix, delta);
+    m_DIFA = (*m_pDIFAs)[i];
+    m_DIFC = (*m_pDIFCs)[i];
+    m_TZERO = (*m_pTZEROs)[i];
+
+    m_TargetUnit->initialize(m_L1, m_Emode,
+                             {{UnitParams::l2, m_L2},
+                              {UnitParams::twoTheta, m_TwoTheta},
+                              {UnitParams::efixed, Efix},
+                              {UnitParams::difa, m_DIFA},
+                              {UnitParams::difc, m_DIFC},
+                              {UnitParams::tzero, m_TZERO}});
     return;
   }
   case (CnvrtToMD::ConvertByTOF): {
-    double delta(std::numeric_limits<double>::quiet_NaN());
     m_TwoTheta = (*m_pTwoThetas)[i];
     m_L2 = (*m_pL2s)[i];
     double Efix = m_Efix;
     if (m_pEfixedArray)
       Efix = static_cast<double>(*(m_pEfixedArray + i));
-
-    m_TargetUnit->initialize(m_L1, m_L2, m_TwoTheta, m_Emode, Efix, delta);
-    m_SourceWSUnit->initialize(m_L1, m_L2, m_TwoTheta, m_Emode, Efix, delta);
+    m_DIFA = (*m_pDIFAs)[i];
+    m_DIFC = (*m_pDIFCs)[i];
+    m_TZERO = (*m_pTZEROs)[i];
+
+    Kernel::UnitParametersMap pmap = {
+        {UnitParams::l2, m_L2},     {UnitParams::twoTheta, m_TwoTheta},
+        {UnitParams::efixed, Efix}, {UnitParams::difa, m_DIFA},
+        {UnitParams::difc, m_DIFC}, {UnitParams::tzero, m_TZERO}};
+    m_TargetUnit->initialize(m_L1, m_Emode, pmap);
+    m_SourceWSUnit->initialize(m_L1, m_Emode, pmap);
     return;
   }
   default:
     throw std::runtime_error(
         "updateConversion: unknown type of conversion requested");
   }
-}
+} // namespace MDAlgorithms
 /** do actual unit conversion from  input to oputput data
 @param   val  -- the input value which has to be converted
 @return          the input value converted into the units requested.
@@ -350,9 +385,15 @@ UnitsConversionHelper::UnitsConversionHelper(
   m_Efix = another.m_Efix;
   m_TwoTheta = another.m_TwoTheta;
   m_L2 = another.m_L2;
+  m_DIFA = another.m_DIFA;
+  m_DIFC = another.m_DIFC;
+  m_TZERO = another.m_TZERO;
   m_pTwoThetas = another.m_pTwoThetas;
   m_pL2s = another.m_pL2s;
   m_pEfixedArray = another.m_pEfixedArray;
+  m_pDIFAs = another.m_pDIFAs;
+  m_pDIFCs = another.m_pDIFCs;
+  m_pTZEROs = another.m_pTZEROs;
 
   if (another.m_SourceWSUnit)
     m_SourceWSUnit = Kernel::Unit_sptr(another.m_SourceWSUnit->clone());
@@ -363,8 +404,10 @@ UnitsConversionHelper::UnitsConversionHelper(
 UnitsConversionHelper::UnitsConversionHelper()
     : m_UnitCnvrsn(CnvrtToMD::ConvertNo), m_Factor(1), m_Power(1),
       m_Emode(-1), // undefined
-      m_L1(1), m_Efix(1), m_TwoTheta(0), m_L2(1), m_pTwoThetas(nullptr),
-      m_pL2s(nullptr), m_pEfixedArray(nullptr) {}
+      m_L1(1), m_Efix(1), m_TwoTheta(0), m_L2(1), m_DIFA(0.), m_DIFC(0.),
+      m_TZERO(0.), m_pTwoThetas(nullptr), m_pL2s(nullptr),
+      m_pEfixedArray(nullptr), m_pDIFAs(nullptr), m_pDIFCs(nullptr),
+      m_pTZEROs(nullptr) {}
 
 } // namespace MDAlgorithms
 } // namespace Mantid
diff --git a/Framework/MDAlgorithms/test/MDTransfModQTest.h b/Framework/MDAlgorithms/test/MDTransfModQTest.h
index d0372773586..73128cba17f 100644
--- a/Framework/MDAlgorithms/test/MDTransfModQTest.h
+++ b/Framework/MDAlgorithms/test/MDTransfModQTest.h
@@ -6,6 +6,7 @@
 // SPDX - License - Identifier: GPL - 3.0 +
 #pragma once
 
+#include "MantidAPI/AlgorithmManager.h"
 #include "MantidGeometry/Instrument/Goniometer.h"
 #include "MantidKernel/DeltaEMode.h"
 #include "MantidMDAlgorithms/MDTransfQ3D.h"
@@ -179,8 +180,14 @@ public:
         ModQTransf.initialize(WSDescr), const std::runtime_error &);
 
     // let's preprocess detectors positions to go any further
-    WSDescr.m_PreprDetTable =
-        WorkspaceCreationHelper::buildPreprocessedDetectorsWorkspace(ws2Dbig);
+    auto ppDets_alg = Mantid::API::AlgorithmManager::Instance().createUnmanaged(
+        "PreprocessDetectorsToMD");
+    ppDets_alg->initialize();
+    ppDets_alg->setChild(true);
+    ppDets_alg->setProperty("InputWorkspace", ws2Dbig);
+    ppDets_alg->setProperty("OutputWorkspace", "UnitsConversionHelperTableWs");
+    ppDets_alg->execute();
+    WSDescr.m_PreprDetTable = ppDets_alg->getProperty("OutputWorkspace");
     TSM_ASSERT_THROWS_NOTHING("should initialize properly: ",
                               ModQTransf.initialize(WSDescr));
     std::vector<coord_t> coord(4);
diff --git a/Framework/MDAlgorithms/test/MDTransfQ3DTest.h b/Framework/MDAlgorithms/test/MDTransfQ3DTest.h
index 73349981b75..f570a2f8d3b 100644
--- a/Framework/MDAlgorithms/test/MDTransfQ3DTest.h
+++ b/Framework/MDAlgorithms/test/MDTransfQ3DTest.h
@@ -6,6 +6,7 @@
 // SPDX - License - Identifier: GPL - 3.0 +
 #pragma once
 
+#include "MantidAPI/AlgorithmManager.h"
 #include "MantidGeometry/Instrument.h"
 #include "MantidGeometry/Instrument/Goniometer.h"
 #include "MantidKernel/DeltaEMode.h"
@@ -43,9 +44,14 @@ createTestTransform(const double efixed, const DeltaEMode::Type emode) {
   wsDescription.setMinMax({-100, -100, -100, -100}, {100, 100, 100, 100});
   wsDescription.buildFromMatrixWS(indirectInelasticWS, q3dTransform.transfID(),
                                   DeltaEMode::asString(emode), {});
-  wsDescription.m_PreprDetTable =
-      WorkspaceCreationHelper::buildPreprocessedDetectorsWorkspace(
-          indirectInelasticWS);
+  auto ppDets_alg = Mantid::API::AlgorithmManager::Instance().createUnmanaged(
+      "PreprocessDetectorsToMD");
+  ppDets_alg->initialize();
+  ppDets_alg->setChild(true);
+  ppDets_alg->setProperty("InputWorkspace", indirectInelasticWS);
+  ppDets_alg->setProperty("OutputWorkspace", "UnitsConversionHelperTableWs");
+  ppDets_alg->execute();
+  wsDescription.m_PreprDetTable = ppDets_alg->getProperty("OutputWorkspace");
   q3dTransform.initialize(wsDescription);
 
   return std::make_tuple(q3dTransform, wsDescription);
@@ -147,9 +153,14 @@ public:
         Q3DTransf.initialize(wsDescription), const std::runtime_error &)
 
     // let's preprocess detectors positions to go any further
-    wsDescription.m_PreprDetTable =
-        WorkspaceCreationHelper::buildPreprocessedDetectorsWorkspace(
-            elasticTestWS);
+    auto ppDets_alg = Mantid::API::AlgorithmManager::Instance().createUnmanaged(
+        "PreprocessDetectorsToMD");
+    ppDets_alg->initialize();
+    ppDets_alg->setChild(true);
+    ppDets_alg->setProperty("InputWorkspace", elasticTestWS);
+    ppDets_alg->setProperty("OutputWorkspace", "UnitsConversionHelperTableWs");
+    ppDets_alg->execute();
+    wsDescription.m_PreprDetTable = ppDets_alg->getProperty("OutputWorkspace");
     // let's set 2Theta=0 for simplicity and violate const correctness for
     // testing purposes here
     auto &TwoTheta = const_cast<std::vector<double> &>(
diff --git a/Framework/MDAlgorithms/test/PreprocessDetectorsToMDTest.h b/Framework/MDAlgorithms/test/PreprocessDetectorsToMDTest.h
index 8cb1315a6c9..6aec885ed33 100644
--- a/Framework/MDAlgorithms/test/PreprocessDetectorsToMDTest.h
+++ b/Framework/MDAlgorithms/test/PreprocessDetectorsToMDTest.h
@@ -55,7 +55,7 @@ public:
     TS_ASSERT(tws);
 
     TS_ASSERT_EQUALS(4, tws->rowCount());
-    TS_ASSERT_EQUALS(8, tws->columnCount());
+    TS_ASSERT_EQUALS(11, tws->columnCount());
   }
 
   void testPreprocessDetectors() {
diff --git a/Framework/MDAlgorithms/test/UnitsConversionHelperTest.h b/Framework/MDAlgorithms/test/UnitsConversionHelperTest.h
index ca9f8b0f088..ce0d1fd493d 100644
--- a/Framework/MDAlgorithms/test/UnitsConversionHelperTest.h
+++ b/Framework/MDAlgorithms/test/UnitsConversionHelperTest.h
@@ -6,6 +6,7 @@
 // SPDX - License - Identifier: GPL - 3.0 +
 #pragma once
 
+#include "MantidAPI/AlgorithmManager.h"
 #include "MantidAPI/FrameworkManager.h"
 #include "MantidAPI/NumericAxis.h"
 #include "MantidKernel/PhysicalConstants.h"
@@ -45,13 +46,19 @@ public:
 
     auto pSourceWSUnit = UnitFactory::Instance().create("Wavelength");
     auto pWSUnit = UnitFactory::Instance().create("MomentumTransfer");
-    double delta;
     double L1(10), L2(10), TwoTheta(0.1), efix(10);
+
     int emode(0);
     TS_ASSERT_THROWS_NOTHING(
-        pWSUnit->initialize(L1, L2, TwoTheta, emode, efix, delta));
+        pWSUnit->initialize(L1, emode,
+                            {{UnitParams::l2, L2},
+                             {UnitParams::twoTheta, TwoTheta},
+                             {UnitParams::efixed, efix}}));
     TS_ASSERT_THROWS_NOTHING(
-        pSourceWSUnit->initialize(L1, L2, TwoTheta, emode, efix, delta));
+        pSourceWSUnit->initialize(L1, emode,
+                                  {{UnitParams::l2, L2},
+                                   {UnitParams::twoTheta, TwoTheta},
+                                   {UnitParams::efixed, efix}}));
 
     double X0(5);
     double tof(0);
@@ -287,6 +294,13 @@ public:
     ws2D = WorkspaceCreationHelper::createProcessedInelasticWS(
         L2, polar, azimutal, numBins, -1, 3, 3);
 
-    detLoc = WorkspaceCreationHelper::buildPreprocessedDetectorsWorkspace(ws2D);
+    auto ppDets_alg = Mantid::API::AlgorithmManager::Instance().createUnmanaged(
+        "PreprocessDetectorsToMD");
+    ppDets_alg->initialize();
+    ppDets_alg->setChild(true);
+    ppDets_alg->setProperty("InputWorkspace", ws2D);
+    ppDets_alg->setProperty("OutputWorkspace", "UnitsConversionHelperTableWs");
+    ppDets_alg->execute();
+    detLoc = ppDets_alg->getProperty("OutputWorkspace");
   }
 };
\ No newline at end of file
diff --git a/Framework/PythonInterface/mantid/kernel/src/Exports/Unit.cpp b/Framework/PythonInterface/mantid/kernel/src/Exports/Unit.cpp
index dd8458c298e..9d750c5d1ba 100644
--- a/Framework/PythonInterface/mantid/kernel/src/Exports/Unit.cpp
+++ b/Framework/PythonInterface/mantid/kernel/src/Exports/Unit.cpp
@@ -8,6 +8,7 @@
 #include "MantidPythonInterface/core/GetPointer.h"
 
 #include <boost/python/class.hpp>
+#include <boost/python/enum.hpp>
 #include <boost/python/register_ptr_to_python.hpp>
 #include <boost/python/tuple.hpp>
 
@@ -57,6 +58,15 @@ void export_Unit() {
 
   register_ptr_to_python<std::shared_ptr<Unit>>();
 
+  enum_<Mantid::Kernel::UnitParams>("UnitParams")
+      .value("l2", Mantid::Kernel::UnitParams::l2)
+      .value("twoTheta", Mantid::Kernel::UnitParams::twoTheta)
+      .value("delta", Mantid::Kernel::UnitParams::delta)
+      .value("efixed", Mantid::Kernel::UnitParams::efixed)
+      .value("difa", Mantid::Kernel::UnitParams::difa)
+      .value("difc", Mantid::Kernel::UnitParams::difc)
+      .value("tzero", Mantid::Kernel::UnitParams::tzero);
+
   class_<Unit, boost::noncopyable>("Unit", no_init)
       .def("name", &deprecatedName, arg("self"),
            "Return the full name of the unit (deprecated, use caption)")
diff --git a/Framework/PythonInterface/mantid/kernel/src/Exports/UnitConversion.cpp b/Framework/PythonInterface/mantid/kernel/src/Exports/UnitConversion.cpp
index 7e371ad185e..0579e672578 100644
--- a/Framework/PythonInterface/mantid/kernel/src/Exports/UnitConversion.cpp
+++ b/Framework/PythonInterface/mantid/kernel/src/Exports/UnitConversion.cpp
@@ -6,21 +6,42 @@
 // SPDX - License - Identifier: GPL - 3.0 +
 #include "MantidKernel/UnitConversion.h"
 #include <boost/python/class.hpp>
+#include <boost/python/suite/indexing/map_indexing_suite.hpp>
 
 using Mantid::Kernel::DeltaEMode;
 using Mantid::Kernel::UnitConversion;
 using namespace boost::python;
 
+double deprecatedSignature(const std::string &src, const std::string &dest,
+                           const double srcValue, const double l1,
+                           const double l2, const double theta,
+                           const DeltaEMode::Type emode, const double efixed) {
+  PyErr_Warn(
+      PyExc_DeprecationWarning,
+      ".run(src, dest, srcValue, l1, l2, theta, emode, efixed) is deprecated. "
+      "Use .run(src, dest, srcValue, l1, emode, params) instead.");
+  return UnitConversion::run(src, dest, srcValue, l1, l2, theta, emode, efixed);
+}
+
 void export_UnitConversion() {
   // Function pointer typedef
-  using StringVersion = double (*)(
+  using newStringVersion = double (*)(
       const std::string &, const std::string &, const double, const double,
-      const double, const double, const DeltaEMode::Type, const double);
+      const DeltaEMode::Type, const Mantid::Kernel::UnitParametersMap &);
+
+  class_<std::unordered_map<Mantid::Kernel::UnitParams, double>>(
+      "UnitParametersMap")
+      .def(map_indexing_suite<
+           std::unordered_map<Mantid::Kernel::UnitParams, double>>());
 
   class_<UnitConversion, boost::noncopyable>("UnitConversion", no_init)
-      .def("run", (StringVersion)&UnitConversion::run,
+      .def("run", &deprecatedSignature,
            (arg("src"), arg("dest"), arg("srcValue"), arg("l1"), arg("l2"),
             arg("theta"), arg("emode"), arg("efixed")),
+           "Performs a unit conversion on a single value (deprecated).")
+      .def("run", (newStringVersion)&UnitConversion::run,
+           (arg("src"), arg("dest"), arg("srcValue"), arg("l1"), arg("emode"),
+            arg("params")),
            "Performs a unit conversion on a single value.")
       .staticmethod("run");
 }
diff --git a/Framework/PythonInterface/test/python/mantid/kernel/UnitConversionTest.py b/Framework/PythonInterface/test/python/mantid/kernel/UnitConversionTest.py
index 4b8ebf717e1..4bc63af2494 100644
--- a/Framework/PythonInterface/test/python/mantid/kernel/UnitConversionTest.py
+++ b/Framework/PythonInterface/test/python/mantid/kernel/UnitConversionTest.py
@@ -5,7 +5,7 @@
 #   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
 # SPDX - License - Identifier: GPL - 3.0 +
 import unittest
-from mantid.kernel import UnitConversion, DeltaEModeType
+from mantid.kernel import UnitConversion, DeltaEModeType, UnitParams, UnitParametersMap
 import math
 
 
@@ -23,5 +23,22 @@ class UnitConversionTest(unittest.TestCase):
         result = UnitConversion.run(src_unit, dest_unit, src_value, l1, l2, theta, emode, efixed)
         self.assertAlmostEqual(result, expected, 12)
 
+    def test_run_accepts_params_version(self):
+        src_unit = "Wavelength"
+        src_value = 1.5
+        dest_unit = "Momentum"
+
+        l1 = l2 = theta = efixed = 0.0
+        emode = DeltaEModeType.Indirect;
+        expected = 2.0*math.pi/src_value
+        params = UnitParametersMap()
+        params[UnitParams.l2] = l2
+        params[UnitParams.twoTheta] = theta
+        # Haven't got a dictionary to convert automatically into std::unordered_map yet
+        #params = {UnitParams.l2: l2, UnitParams.twoTheta: theta}
+
+        result = UnitConversion.run(src_unit, dest_unit, src_value, l1, emode, params)
+        self.assertAlmostEqual(result, expected, 12)
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/Framework/SINQ/src/PoldiPeakSearch.cpp b/Framework/SINQ/src/PoldiPeakSearch.cpp
index d16c4966824..519ecf47cd4 100644
--- a/Framework/SINQ/src/PoldiPeakSearch.cpp
+++ b/Framework/SINQ/src/PoldiPeakSearch.cpp
@@ -224,8 +224,8 @@ double PoldiPeakSearch::getTransformedCenter(double value,
 
   // Transform value to d-spacing.
   Unit_sptr dUnit = UnitFactory::Instance().create("dSpacing");
-  return UnitConversion::run((*transformUnit), (*dUnit), value, 0, 0, 0,
-                             DeltaEMode::Elastic, 0.0);
+  return UnitConversion::run((*transformUnit), (*dUnit), value, 0,
+                             DeltaEMode::Elastic);
 }
 
 /** Creates PoldiPeak-objects from peak position iterators
diff --git a/Framework/TestHelpers/src/WorkspaceCreationHelper.cpp b/Framework/TestHelpers/src/WorkspaceCreationHelper.cpp
index aa0b53078a1..e405ff29092 100644
--- a/Framework/TestHelpers/src/WorkspaceCreationHelper.cpp
+++ b/Framework/TestHelpers/src/WorkspaceCreationHelper.cpp
@@ -1356,134 +1356,6 @@ createPeaksWorkspace(const int numPeaks,
   return peaksWS;
 }
 
-/** helper method to create preprocessed detector's table workspace */
-std::shared_ptr<DataObjects::TableWorkspace>
-createTableWorkspace(const API::MatrixWorkspace_const_sptr &inputWS) {
-  const size_t nHist = inputWS->getNumberHistograms();
-
-  // set the target workspace
-  auto targWS = std::make_shared<TableWorkspace>(nHist);
-  // detectors positions
-  targWS->addColumn("V3D", "DetDirections");
-  // sample-detector distance;
-  targWS->addColumn("double", "L2");
-  // Diffraction angle
-  targWS->addColumn("double", "TwoTheta");
-  targWS->addColumn("double", "Azimuthal");
-  // the detector ID;
-  targWS->addColumn("int", "DetectorID");
-  // stores spectra index which corresponds to a valid detector index;
-  targWS->addColumn("size_t", "detIDMap");
-  // stores detector index which corresponds to the workspace index;
-  targWS->addColumn("size_t", "spec2detMap");
-
-  // will see about that
-  // sin^2(Theta)
-  //    std::vector<double>      SinThetaSq;
-
-  //,"If the detectors were actually processed from real instrument or generated
-  // for some fake one ");
-  return targWS;
-}
-
-/** method does preliminary calculations of the detectors positions to convert
- results into k-dE space ;
- and places the results into static cash to be used in subsequent calls to this
- algorithm */
-void processDetectorsPositions(const API::MatrixWorkspace_const_sptr &inputWS,
-                               DataObjects::TableWorkspace_sptr &targWS,
-                               double Ei) {
-  Geometry::Instrument_const_sptr instrument = inputWS->getInstrument();
-  //
-  Geometry::IComponent_const_sptr source = instrument->getSource();
-  Geometry::IComponent_const_sptr sample = instrument->getSample();
-  if ((!source) || (!sample)) {
-
-    throw Kernel::Exception::InstrumentDefinitionError(
-        "Instrument not sufficiently defined: failed to get source and/or "
-        "sample");
-  }
-
-  // L1
-  try {
-    double L1 = source->getDistance(*sample);
-    targWS->logs()->addProperty<double>("L1", L1, true);
-  } catch (Kernel::Exception::NotFoundError &) {
-    throw Kernel::Exception::InstrumentDefinitionError(
-        "Unable to calculate source-sample distance for workspace",
-        inputWS->getTitle());
-  }
-  // Instrument name
-  std::string InstrName = instrument->getName();
-  targWS->logs()->addProperty<std::string>("InstrumentName", InstrName, true);
-  targWS->logs()->addProperty<bool>("FakeDetectors", false, true);
-  // Incident energy for Direct or Analysis energy for indirect instrument;
-  targWS->logs()->addProperty<double>("Ei", Ei, true);
-
-  // get access to the workspace memory
-  auto &sp2detMap = targWS->getColVector<size_t>("spec2detMap");
-  auto &detId = targWS->getColVector<int32_t>("DetectorID");
-  auto &detIDMap = targWS->getColVector<size_t>("detIDMap");
-  auto &L2 = targWS->getColVector<double>("L2");
-  auto &TwoTheta = targWS->getColVector<double>("TwoTheta");
-  auto &Azimuthal = targWS->getColVector<double>("Azimuthal");
-  auto &detDir = targWS->getColVector<Kernel::V3D>("DetDirections");
-
-  //// progress messave appearence
-  size_t nHist = targWS->rowCount();
-  //// Loop over the spectra
-  uint32_t liveDetectorsCount(0);
-  const auto &spectrumInfo = inputWS->spectrumInfo();
-  for (size_t i = 0; i < nHist; i++) {
-    sp2detMap[i] = std::numeric_limits<size_t>::quiet_NaN();
-    detId[i] = std::numeric_limits<int32_t>::quiet_NaN();
-    detIDMap[i] = std::numeric_limits<size_t>::quiet_NaN();
-    L2[i] = std::numeric_limits<double>::quiet_NaN();
-    TwoTheta[i] = std::numeric_limits<double>::quiet_NaN();
-    Azimuthal[i] = std::numeric_limits<double>::quiet_NaN();
-
-    if (!spectrumInfo.hasDetectors(i) || spectrumInfo.isMonitor(i))
-      continue;
-
-    // calculate the requested values;
-    sp2detMap[i] = liveDetectorsCount;
-    detId[liveDetectorsCount] = int32_t(spectrumInfo.detector(i).getID());
-    detIDMap[liveDetectorsCount] = i;
-    L2[liveDetectorsCount] = spectrumInfo.l2(i);
-
-    double polar = spectrumInfo.twoTheta(i);
-    double azim = spectrumInfo.detector(i).getPhi();
-    TwoTheta[liveDetectorsCount] = polar;
-    Azimuthal[liveDetectorsCount] = azim;
-
-    double sPhi = sin(polar);
-    double ez = cos(polar);
-    double ex = sPhi * cos(azim);
-    double ey = sPhi * sin(azim);
-
-    detDir[liveDetectorsCount].setX(ex);
-    detDir[liveDetectorsCount].setY(ey);
-    detDir[liveDetectorsCount].setZ(ez);
-
-    // double sinTheta=sin(0.5*polar);
-    // this->SinThetaSq[liveDetectorsCount]  = sinTheta*sinTheta;
-
-    liveDetectorsCount++;
-  }
-  targWS->logs()->addProperty<uint32_t>(
-      "ActualDetectorsNum", liveDetectorsCount,
-      true); //,"The actual number of detectors receivinv signal");
-}
-
-std::shared_ptr<Mantid::DataObjects::TableWorkspace>
-buildPreprocessedDetectorsWorkspace(
-    const Mantid::API::MatrixWorkspace_sptr &ws) {
-  Mantid::DataObjects::TableWorkspace_sptr DetPos = createTableWorkspace(ws);
-  auto Ei = ws->run().getPropertyValueAsType<double>("Ei");
-  processDetectorsPositions(ws, DetPos, Ei);
-
-  return DetPos;
-}
 void create2DAngles(std::vector<double> &L2, std::vector<double> &polar,
                     std::vector<double> &azim, size_t nPolar, size_t nAzim,
                     double polStart, double polEnd, double azimStart,
diff --git a/Testing/Data/SystemTest/ISIS_Powder/input/calibration/hrp/16_5/ISIS_Powder-HRPD-VanSplined_66031_hrpd_new_072_01_corr.cal.nxs.md5 b/Testing/Data/SystemTest/ISIS_Powder/input/calibration/hrp/16_5/ISIS_Powder-HRPD-VanSplined_66031_hrpd_new_072_01_corr.cal.nxs.md5
index e932090757c..74b69e93ad5 100644
--- a/Testing/Data/SystemTest/ISIS_Powder/input/calibration/hrp/16_5/ISIS_Powder-HRPD-VanSplined_66031_hrpd_new_072_01_corr.cal.nxs.md5
+++ b/Testing/Data/SystemTest/ISIS_Powder/input/calibration/hrp/16_5/ISIS_Powder-HRPD-VanSplined_66031_hrpd_new_072_01_corr.cal.nxs.md5
@@ -1 +1 @@
-a2ec43e892f8d2188afb7ab40fa314b0
+608f0a5a0776e7c4318c277306409a75
diff --git a/Testing/SystemTests/tests/framework/EnginXScriptTest.py b/Testing/SystemTests/tests/framework/EnginXScriptTest.py
index 21592257bd7..73383e66b73 100644
--- a/Testing/SystemTests/tests/framework/EnginXScriptTest.py
+++ b/Testing/SystemTests/tests/framework/EnginXScriptTest.py
@@ -10,7 +10,7 @@ import systemtesting
 
 import mantid.simpleapi as simple
 
-from mantid import config
+from mantid import config, mtd
 from Engineering.EnginX import main
 
 DIRS = config['datasearch.directories'].split(';')
@@ -73,19 +73,28 @@ class CreateCalibrationCroppedTest(systemtesting.MantidSystemTest):
         main(vanadium_run="236516", user="test", focus_run=None, do_cal=True, force_cal=True, directory=cal_directory,
              crop_type="spectra", crop_on="1-20")
 
+    def validateSingleColumn(self, col1, col2, tolerance):
+        assert len(col1)==len(col2)
+        for a, b in zip(col1, col2):
+            den = 0.5 * (abs(a) + abs(b))
+            assert (abs(a - b) / den) < tolerance
+
     def validate(self):
         self.tolerance_is_rel_err = True
         self.tolerance = 1e-2
 
         # this is neccesary due to appendspectra creating spectrum numbers of 0
         self.disableChecking.append('SpectraMap')
+        # fitted peaks table workspace is v sensitive to changes in input data so just validate X0 col
         if systemtesting.using_gsl_v1():
-            return ("cropped", "engggui_calibration_bank_cropped_gsl1.nxs",
-                    "engg_calibration_banks_parameters", "engggui_calibration_cropped_parameters_gsl1.nxs",
+            reffile = simple.LoadNexus(Filename="engggui_calibration_bank_cropped_gsl1.nxs")
+            self.validateSingleColumn(mtd["cropped"].column("X0"),reffile.column("X0"),self.tolerance)
+            return ("engg_calibration_banks_parameters", "engggui_calibration_cropped_parameters_gsl1.nxs",
                     "Engg difc Zero Peaks Bank cropped", "engggui_difc_zero_peaks_bank_cropped_gsl1.nxs")
         else:
-            return ("cropped", "engggui_calibration_bank_cropped.nxs",
-                    "engg_calibration_banks_parameters", "engggui_calibration_bank_cropped_parameters.nxs",
+            reffile = simple.LoadNexus(Filename="engggui_calibration_bank_cropped.nxs")
+            self.validateSingleColumn(mtd["cropped"].column("X0"),reffile.column("X0"),self.tolerance)
+            return ("engg_calibration_banks_parameters", "engggui_calibration_bank_cropped_parameters.nxs",
                     "Engg difc Zero Peaks Bank cropped", "engggui_difc_zero_peaks_bank_cropped.nxs")
 
     def cleanup(self):
diff --git a/Testing/SystemTests/tests/framework/ISIS_PowderPolarisTest.py b/Testing/SystemTests/tests/framework/ISIS_PowderPolarisTest.py
index ef4cf93f5cf..ab3e737e644 100644
--- a/Testing/SystemTests/tests/framework/ISIS_PowderPolarisTest.py
+++ b/Testing/SystemTests/tests/framework/ISIS_PowderPolarisTest.py
@@ -63,8 +63,8 @@ class CreateVanadiumTest(systemtesting.MantidSystemTest):
         splined_ws, unsplined_ws = self.calibration_results
         for ws in splined_ws+unsplined_ws:
             self.assertEqual(ws.sample().getMaterial().name(), 'V')
-        return (unsplined_ws.name(), "ISIS_Powder-POLARIS00098533_unsplined.nxs",
-                splined_ws.name(), "ISIS_Powder-POLARIS00098533_splined.nxs")
+        return (unsplined_ws.name(), "ISIS_Powder-POLARIS00098532_unsplined.nxs",
+                splined_ws.name(), "ISIS_Powder-POLARIS00098532_splined.nxs")
 
     def cleanup(self):
         try:
@@ -108,7 +108,8 @@ class FocusTest(systemtesting.MantidSystemTest):
 
         for ws in self.focus_results:
             self.assertEqual(ws.sample().getMaterial().name(), 'Si')
-        self.tolerance = 1e-7
+        self.tolerance_is_rel_err = True
+        self.tolerance = 1e-6
         return self.focus_results.name(), "ISIS_Powder-POLARIS98533_FocusSempty.nxs"
 
     def cleanup(self):
diff --git a/Testing/SystemTests/tests/framework/reference/HRPD66063_focused.nxs.md5 b/Testing/SystemTests/tests/framework/reference/HRPD66063_focused.nxs.md5
index e15d64c7d35..cfc032862af 100644
--- a/Testing/SystemTests/tests/framework/reference/HRPD66063_focused.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/HRPD66063_focused.nxs.md5
@@ -1 +1 @@
-8c3a6b213a055248e8a24a7057c095d6
+1be2026d1de55fd2bed0a6f8ddfc4d39
diff --git a/Testing/SystemTests/tests/framework/reference/HRPD66063_focused_with_sac.nxs.md5 b/Testing/SystemTests/tests/framework/reference/HRPD66063_focused_with_sac.nxs.md5
index 7722a1b9661..4c86bda5634 100644
--- a/Testing/SystemTests/tests/framework/reference/HRPD66063_focused_with_sac.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/HRPD66063_focused_with_sac.nxs.md5
@@ -1 +1 @@
-4e22b59dcf154fe612664caad97106a5
+37ca9e41dadb8209c48a94456b293424
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-GEM83605_FocusSempty.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-GEM83605_FocusSempty.nxs.md5
index 829a5bf2358..14147199e4f 100644
--- a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-GEM83605_FocusSempty.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-GEM83605_FocusSempty.nxs.md5
@@ -1 +1 @@
-66daa3c0ca467551327f0fab16603176
+ecea38a404523a3760af402b23ba138d
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-GEM83605_FocusSempty_abscorr.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-GEM83605_FocusSempty_abscorr.nxs.md5
index 46f87415aa7..450cc2132b9 100644
--- a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-GEM83605_FocusSempty_abscorr.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-GEM83605_FocusSempty_abscorr.nxs.md5
@@ -1 +1 @@
-4449362e48a44a6b9dd022e94bbea290
+3a4603faeb65bdac0c39438df73b6ae1
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-GEM87618_grouped.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-GEM87618_grouped.nxs.md5
index 99802ed127e..ae217da0e72 100644
--- a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-GEM87618_grouped.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-GEM87618_grouped.nxs.md5
@@ -1 +1 @@
-66c9c9248b6aa178ea50be7722aebaca
+49479cfe5b2ef222d6fef8e7262b33fe
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-GEM87618_groupedGSAS1.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-GEM87618_groupedGSAS1.nxs.md5
index 88231da7cc1..7db8702743e 100644
--- a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-GEM87618_groupedGSAS1.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-GEM87618_groupedGSAS1.nxs.md5
@@ -1 +1 @@
-8e69351a25f506a59d3de8ca6a68403a
+9cf6f0ffda00dfcad3d186590434b7d5
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL00098472_splined.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL00098472_splined.nxs.md5
index 52b72dec866..1c048e99c58 100644
--- a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL00098472_splined.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL00098472_splined.nxs.md5
@@ -1 +1 @@
-a828d3c74ab4ce5876aae29e61794f22
+912967bbcca604e98b02540b2d1d069d
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL00098507_tt70Atten.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL00098507_tt70Atten.nxs.md5
index 62a7f42f936..66f96e555bb 100644
--- a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL00098507_tt70Atten.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL00098507_tt70Atten.nxs.md5
@@ -1 +1 @@
-1b79639f158a73a1f1f4d76da14f4ae4
+fdacd843f9fbe1f8309fcc55d75ffa3a
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL00098507_tt70NoEmptySub.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL00098507_tt70NoEmptySub.nxs.md5
index ba8e617da7b..b8dcb8acae0 100644
--- a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL00098507_tt70NoEmptySub.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL00098507_tt70NoEmptySub.nxs.md5
@@ -1 +1 @@
-205ea520333c267369b8c750b7c8afaf
+1d4149c223976a13cc0231315fd8a423
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL00098507_tt70_absorb.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL00098507_tt70_absorb.nxs.md5
index 74e9ea2361d..769f6dfcd6d 100644
--- a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL00098507_tt70_absorb.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL00098507_tt70_absorb.nxs.md5
@@ -1 +1 @@
-a22807ee5cd6565886832ddba9cb3f16
+7ec38fcc0e659a3941b36a5384a309a3
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL98494_grouped.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL98494_grouped.nxs.md5
index 85159fd3ae5..3a984c30260 100644
--- a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL98494_grouped.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL98494_grouped.nxs.md5
@@ -1 +1 @@
-b957a6b0928e12a8c86284ea010df429
\ No newline at end of file
+de74dd3f5c3df66740a0f3b5c955b16d
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-POLARIS00098533_splined.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-POLARIS00098532_splined.nxs.md5
similarity index 100%
rename from Testing/SystemTests/tests/framework/reference/ISIS_Powder-POLARIS00098533_splined.nxs.md5
rename to Testing/SystemTests/tests/framework/reference/ISIS_Powder-POLARIS00098532_splined.nxs.md5
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-POLARIS00098533_unsplined.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-POLARIS00098532_unsplined.nxs.md5
similarity index 100%
rename from Testing/SystemTests/tests/framework/reference/ISIS_Powder-POLARIS00098533_unsplined.nxs.md5
rename to Testing/SystemTests/tests/framework/reference/ISIS_Powder-POLARIS00098532_unsplined.nxs.md5
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-POLARIS98533_Auto_chopper.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-POLARIS98533_Auto_chopper.nxs.md5
index 1495b831705..5b6652bd190 100644
--- a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-POLARIS98533_Auto_chopper.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-POLARIS98533_Auto_chopper.nxs.md5
@@ -1 +1 @@
-ea4a0c8a223ab9f0fe8d06fc9e1604d3
+d5cd699c2749e999f2273f78c08fff47
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-POLARIS98533_FocusSempty.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-POLARIS98533_FocusSempty.nxs.md5
index 37c1f1bed76..5b3ac0c44df 100644
--- a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-POLARIS98533_FocusSempty.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-POLARIS98533_FocusSempty.nxs.md5
@@ -1 +1 @@
-281e4969b42ff1e891bf3c1377bdc8de
+7d22193e2a7634cfe359b95f94e4dab7
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder_PRL98472_tt70_all.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder_PRL98472_tt70_all.nxs.md5
index 2998cab6e1c..3e757cf3177 100644
--- a/Testing/SystemTests/tests/framework/reference/ISIS_Powder_PRL98472_tt70_all.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/ISIS_Powder_PRL98472_tt70_all.nxs.md5
@@ -1 +1 @@
-92c4276dd271f2ad8d5e7bb37c089b57
+f1a2f87e9dac226355703b9e61cb82f0
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder_PRL98472_tt70_groups.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder_PRL98472_tt70_groups.nxs.md5
index c282ebfdfbd..ca5e1efc08b 100644
--- a/Testing/SystemTests/tests/framework/reference/ISIS_Powder_PRL98472_tt70_groups.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/ISIS_Powder_PRL98472_tt70_groups.nxs.md5
@@ -1 +1 @@
-3c19d9a0702c746db0b0ddb6a760abca
+80892f681d9cb6f598e50c57294ba44f
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder_PRL98472_tt70_mods.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder_PRL98472_tt70_mods.nxs.md5
index 5211bfef5af..af4d09cc580 100644
--- a/Testing/SystemTests/tests/framework/reference/ISIS_Powder_PRL98472_tt70_mods.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/ISIS_Powder_PRL98472_tt70_mods.nxs.md5
@@ -1 +1 @@
-1f5ba298ba0297181053bbae8c470a64
+ad8b3130bc3821f7f678798483a244fd
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder_PRL98472_tt70_trans.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder_PRL98472_tt70_trans.nxs.md5
index 47f45cfc401..651fa9fe230 100644
--- a/Testing/SystemTests/tests/framework/reference/ISIS_Powder_PRL98472_tt70_trans.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/ISIS_Powder_PRL98472_tt70_trans.nxs.md5
@@ -1 +1 @@
-719362df6faebf097f07fc95ecbaba9c
+76defafd738c04e38fa289bff568e294
diff --git a/Testing/SystemTests/tests/framework/reference/PG3_4844_reference.gsa.md5 b/Testing/SystemTests/tests/framework/reference/PG3_4844_reference.gsa.md5
index f5c92d08a9a..98d9db432d9 100644
--- a/Testing/SystemTests/tests/framework/reference/PG3_4844_reference.gsa.md5
+++ b/Testing/SystemTests/tests/framework/reference/PG3_4844_reference.gsa.md5
@@ -1 +1 @@
-8882c0f4cc428087529940ba49e3bc08
+dbde6312703bb8ab2de56edd29384e18
diff --git a/Testing/SystemTests/tests/framework/reference/PG3_4866_reference.gsa.md5 b/Testing/SystemTests/tests/framework/reference/PG3_4866_reference.gsa.md5
index 1d04c3a13d0..58dbd552445 100644
--- a/Testing/SystemTests/tests/framework/reference/PG3_4866_reference.gsa.md5
+++ b/Testing/SystemTests/tests/framework/reference/PG3_4866_reference.gsa.md5
@@ -1 +1 @@
-27a8c7ff7c64574ea6034632f367c991
+b11628262a659bcb05a1368cd3ebe7c6
diff --git a/docs/source/algorithms/ApplyDiffCal-v1.rst b/docs/source/algorithms/ApplyDiffCal-v1.rst
new file mode 100644
index 00000000000..7e58cc152f5
--- /dev/null
+++ b/docs/source/algorithms/ApplyDiffCal-v1.rst
@@ -0,0 +1,36 @@
+.. algorithm::
+
+.. summary::
+
+.. relatedalgorithms::
+
+.. properties::
+
+Description
+-----------
+
+Loads diffractometer constants (DIFA, DIFC, TZERO) from a calibration data source and
+stores them in the instrument parameter map of the `InstrumentWorkspace`. The constants
+are used in time of flight diffraction instruments to convert from time of flight to 
+d spacing and vice versa.
+
+This algorithm either reads the constants from the
+`CalibrationWorkspace`, reads them from `CalibrationFile` using :ref:`LoadDiffCal
+<algm-LoadDiffCal>`, or uses :ref:`ConvertDiffCal<algm-ConvertDiffCal>` to generate 
+them from the `OffsetsWorkspace`.
+
+This algorithm allows unit conversions between time of flight and d spacing using
+calibrated diffractometer constants to be performed using the
+:ref:`ConvertUnits <algm-ConvertUnits>` algorithm. :ref:`ConvertUnits <algm-ConvertUnits>`
+reads the constants from the instrument parameter map.
+
+When used together with :ref:`ConvertUnits <algm-ConvertUnits>` this algorithm provides a way of
+converting in both directions between time of flight and d spacing and it is an alternative to
+using :ref:`AlignDetectors <algm-AlignDetectors>`.
+
+The values of the diffractometer constants that are stored in the instrument parameter map
+can be viewed on the Show Detectors screen of a workspace.
+
+.. categories::
+
+.. sourcelink::
diff --git a/docs/source/algorithms/PreprocessDetectorsToMD-v1.rst b/docs/source/algorithms/PreprocessDetectorsToMD-v1.rst
index 7d000a25ae2..2574019ba96 100644
--- a/docs/source/algorithms/PreprocessDetectorsToMD-v1.rst
+++ b/docs/source/algorithms/PreprocessDetectorsToMD-v1.rst
@@ -82,7 +82,7 @@ Usage
 .. testoutput:: ExPreprocessDetectoresToMD
 
    The resulting table has the following columns:
-   ['DetDirections', 'L2', 'TwoTheta', 'Azimuthal', 'DetectorID', 'detIDMap', 'spec2detMap', 'detMask', 'eFixed']
+   ['DetDirections', 'L2', 'TwoTheta', 'Azimuthal', 'DetectorID', 'detIDMap', 'spec2detMap', 'detMask', 'eFixed', 'DIFA', 'DIFC', 'TZERO']
    The number of rows in the workspace is :  918
    The polar angle for detector N 10 is 0.314159 rad
    The table workspace logs (properties) are currently not available from python
diff --git a/docs/source/concepts/UnitFactory.rst b/docs/source/concepts/UnitFactory.rst
index 789f1076b29..01e75231fe9 100644
--- a/docs/source/concepts/UnitFactory.rst
+++ b/docs/source/concepts/UnitFactory.rst
@@ -33,7 +33,7 @@ The following units are available in the default Mantid distribution. These unit
 +-------------------------------------------+---------------------------------+-----------------------------+------------------------------------------------------------------------------------------------------------------+
 | Momentum (k)                              | Momentum                        | :math:`\mathrm{\AA}^{-1}`   | :math:`k = \frac{2 \pi }{\lambda}=\frac{2 \pi \times m_N \times L_{tot}}{h \times \mathrm{tof}}`                 |
 +-------------------------------------------+---------------------------------+-----------------------------+------------------------------------------------------------------------------------------------------------------+
-| d-spacing                                 | dSpacing                        | :math:`\mathrm{\AA}`        | :math:`d = \frac{n \, \lambda}{2 \, sin \, \theta}`                                                              |
+| d-spacing                                 | dSpacing                        | :math:`\mathrm{\AA}`        | :math:`TOF = DIFA \, d^2 + DIFC d + TZERO` (see below)                                                           |
 +-------------------------------------------+---------------------------------+-----------------------------+------------------------------------------------------------------------------------------------------------------+
 | Momentum transfer (Q)                     | MomentumTransfer                | :math:`\mathrm{\AA}^{-1}`   | :math:`Q = 2 \, k \, sin \, \theta = \frac{4 \pi sin \theta}{\lambda}`                                           |
 +-------------------------------------------+---------------------------------+-----------------------------+------------------------------------------------------------------------------------------------------------------+
@@ -68,6 +68,15 @@ energy respectively. Units conversion into elastic momentum transfer
 (MomentumTransfer) will throw in elastic mode (emode=0) on inelastic
 workspace (when energy transfer is specified along x-axis)
 
+**Note on d-spacing**: The coefficients DIFA, DIFC and TZERO may be obtained 
+via calibration of a TOF diffraction instrument. In the absence of a calibration,
+DIFA=TZERO=0 and the default value of DIFC is:
+
+:math:`DIFC = 10^{-4} \frac{m_N}{h} (L_1 + L_2) 2 \sin(\theta)`
+
+where the scaling factor adjusts for the fact that DIFC is required in units 
+of :math:`\mu s` per :math:`\mathrm{\AA}`.
+
 **d-spacingPerpendicular** is a unit invented in `J. Appl. Cryst. (2015) 48, pp. 1627--1636 <https://doi.org/10.1107/S1600576715016520>`_ for 2D Rietveld refinement
 of angular and wavelength-dispersive neutron time-of-flight powder diffraction data. Together with the d-Spacing :math:`d`,
 d-SpacingPerpendicular :math:`d_{\perp}` forms a new orthogonal coordinate system.
diff --git a/docs/source/release/v6.1.0/diffraction.rst b/docs/source/release/v6.1.0/diffraction.rst
index 03d589f815a..3d8a6cdd954 100644
--- a/docs/source/release/v6.1.0/diffraction.rst
+++ b/docs/source/release/v6.1.0/diffraction.rst
@@ -49,6 +49,8 @@ Bugfixes
 - Allow a different number of spectra for absorption correction division of PEARL data. This allows ``create_vanadium`` to work for a non-standard dataset.
 - Saved filenames for summed empty workspaces now include spline properties to avoid long_mode confusion when focussing.
 
+- The :ref:`ConvertUnits <algm-ConvertUnits>` algorithm has been extended to use a quadratic relationship between d spacing and TOF when doing conversions between these units. The diffractometer constants DIFA, DIFC and TZERO that determine the form of the quadratic can be loaded into a workspace using a new :ref:`ApplyDiffCal <algm-ApplyDiffCal>` algorithm. This functionality was previously only available in :ref:`AlignDetectors <algm-AlignDetectors>` which only performed the conversion in the direction TOF to d spacing. This change will ensure that the conversion of focussed datasets from d spacing back to TOF at the end of the ISIS powder diffraction data reduction is performed correctly.
+
 Engineering Diffraction
 -----------------------
 
diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/ImageInfoModelMatrixWS.h b/qt/widgets/common/inc/MantidQtWidgets/Common/ImageInfoModelMatrixWS.h
index 48f6955e1c8..9a1563b0c52 100644
--- a/qt/widgets/common/inc/MantidQtWidgets/Common/ImageInfoModelMatrixWS.h
+++ b/qt/widgets/common/inc/MantidQtWidgets/Common/ImageInfoModelMatrixWS.h
@@ -43,8 +43,6 @@ public:
 private:
   void setUnitsInfo(ImageInfoModel::ImageInfo *info, int infoIndex,
                     const size_t wsIndex, const double x) const;
-  std::tuple<Mantid::Kernel::DeltaEMode::Type, double>
-  efixedAt(const size_t wsIndex) const;
   void cacheWorkspaceInfo();
   void createItemNames();
 
diff --git a/qt/widgets/common/src/ImageInfoModelMatrixWS.cpp b/qt/widgets/common/src/ImageInfoModelMatrixWS.cpp
index 9a2e687c2f8..caf5ca32762 100644
--- a/qt/widgets/common/src/ImageInfoModelMatrixWS.cpp
+++ b/qt/widgets/common/src/ImageInfoModelMatrixWS.cpp
@@ -105,10 +105,17 @@ void ImageInfoModelMatrixWS::setUnitsInfo(ImageInfoModel::ImageInfo *info,
                                           int infoIndex, const size_t wsIndex,
                                           const double x) const {
   const auto l1 = m_spectrumInfo->l1();
-  const auto l2 = m_spectrumInfo->l2(wsIndex);
-  const auto twoTheta = m_spectrumInfo->twoTheta(wsIndex);
-  const auto [emode, efixed] = efixedAt(wsIndex);
-
+  auto emode = m_workspace->getEMode();
+  UnitParametersMap pmap{};
+  m_spectrumInfo->getDetectorValues(Units::Empty(), Units::Empty(), emode,
+                                    false, wsIndex, pmap);
+  double efixed = 0.;
+  if (pmap.find(UnitParams::efixed) != pmap.end()) {
+    efixed = pmap[UnitParams::efixed];
+  }
+  // if it's not possible to find an efixed we are forced to treat is as elastic
+  if (efixed == 0.0)
+    emode = DeltaEMode::Elastic;
   double tof(0.0);
   const auto &unitFactory = UnitFactory::Instance();
   const auto tofUnit = unitFactory.create("TOF");
@@ -117,8 +124,7 @@ void ImageInfoModelMatrixWS::setUnitsInfo(ImageInfoModel::ImageInfo *info,
     tof = x;
   } else {
     try {
-      tof =
-          m_xunit->convertSingleToTOF(x, l1, l2, twoTheta, emode, efixed, 0.0);
+      tof = m_xunit->convertSingleToTOF(x, l1, emode, pmap);
     } catch (std::exception &exc) {
       // without TOF we can't get to the other units
       if (g_log.is(Logger::Priority::PRIO_DEBUG))
@@ -133,8 +139,7 @@ void ImageInfoModelMatrixWS::setUnitsInfo(ImageInfoModel::ImageInfo *info,
     if (!requiresEFixed || efixed > 0.0) {
       try {
         // the final parameter is unused and a relic
-        const auto unitValue = unit->convertSingleFromTOF(tof, l1, l2, twoTheta,
-                                                          emode, efixed, 0.0);
+        const auto unitValue = unit->convertSingleFromTOF(tof, l1, emode, pmap);
         info->setValue(infoIndex, defaultFormat(unitValue));
       } catch (std::exception &exc) {
         if (g_log.is(Logger::Priority::PRIO_DEBUG))
@@ -228,56 +233,5 @@ void ImageInfoModelMatrixWS::createItemNames() {
   }
 }
 
-/**
- * Return tuple(emode, efixed) for the pixel at the given workspace index
- * @param wsIndex Indeox of spectrum to query
- */
-std::tuple<Mantid::Kernel::DeltaEMode::Type, double>
-ImageInfoModelMatrixWS::efixedAt(const size_t wsIndex) const {
-  DeltaEMode::Type emode = m_workspace->getEMode();
-  double efixed(0.0);
-  if (m_spectrumInfo->isMonitor(wsIndex))
-    return std::make_tuple(emode, efixed);
-
-  if (emode == DeltaEMode::Direct) {
-    const auto &run = m_workspace->run();
-    for (const auto &logName : {"Ei", "EnergyRequested", "EnergyEstimate"}) {
-      if (run.hasProperty(logName)) {
-        efixed = run.getPropertyValueAsType<double>(logName);
-        break;
-      }
-    }
-  } else if (emode == DeltaEMode::Indirect) {
-    const auto &detector = m_spectrumInfo->detector(wsIndex);
-    const ParameterMap &pmap = m_workspace->constInstrumentParameters();
-    try {
-      for (const auto &paramName : {"Efixed", "Efixed-val"}) {
-        auto parameter = pmap.getRecursive(&detector, paramName);
-        if (parameter) {
-          efixed = parameter->value<double>();
-          break;
-        }
-        // check the instrument as if the detector is a group the above
-        // recursion doesn't work
-        parameter = pmap.getRecursive(m_instrument.get(), paramName);
-        if (parameter) {
-          efixed = parameter->value<double>();
-          break;
-        }
-      }
-    } catch (std::runtime_error &exc) {
-      g_log.debug() << "Failed to get efixed from spectrum at index " << wsIndex
-                    << ": " << exc.what() << "\n";
-      efixed = 0.0;
-    }
-  }
-
-  // if it's not possible to find an efixed we are forced to treat is as elastic
-  if (efixed == 0.0)
-    emode = DeltaEMode::Elastic;
-
-  return std::make_tuple(emode, efixed);
-}
-
 } // namespace MantidWidgets
 } // namespace MantidQt
diff --git a/scripts/Diffraction/isis_powder/abstract_inst.py b/scripts/Diffraction/isis_powder/abstract_inst.py
index 62b5a8c40ab..2e402d3142c 100644
--- a/scripts/Diffraction/isis_powder/abstract_inst.py
+++ b/scripts/Diffraction/isis_powder/abstract_inst.py
@@ -7,6 +7,7 @@
 import os
 from isis_powder.routines import calibrate, focus, common, common_enums, common_output
 from mantid.kernel import config, logger
+import mantid.simpleapi as mantid
 # This class provides common hooks for instruments to override
 # if they want to define the behaviour of the hook. Otherwise it
 # returns the object passed in without manipulating it as a default
@@ -276,6 +277,11 @@ class AbstractInst(object):
         """
         return None
 
+    def apply_calibration_to_focused_data(self, focused_ws):
+        # convert back to TOF based on engineered detector positions
+        mantid.ApplyDiffCal(InstrumentWorkspace=focused_ws,
+                            ClearCalibration=True)
+
     # Steps applicable to all instruments
 
     @staticmethod
diff --git a/scripts/Diffraction/isis_powder/routines/calibrate.py b/scripts/Diffraction/isis_powder/routines/calibrate.py
index 58849920419..9e1e42c859c 100644
--- a/scripts/Diffraction/isis_powder/routines/calibrate.py
+++ b/scripts/Diffraction/isis_powder/routines/calibrate.py
@@ -43,21 +43,23 @@ def create_van(instrument, run_details, absorb):
         # Assume that create_van only uses Vanadium runs
         mantid.SetSampleMaterial(InputWorkspace=corrected_van_ws, ChemicalFormula='V')
 
-    aligned_ws = mantid.AlignDetectors(InputWorkspace=corrected_van_ws,
-                                       CalibrationFile=run_details.offset_file_path)
+    mantid.ApplyDiffCal(InstrumentWorkspace=corrected_van_ws,
+                        CalibrationFile=run_details.offset_file_path)
+    aligned_ws = mantid.ConvertUnits(InputWorkspace=corrected_van_ws, Target="dSpacing")
     solid_angle = instrument.get_solid_angle_corrections(run_details.run_number, run_details)
     if solid_angle:
         aligned_ws = mantid.Divide(LHSWorkspace=aligned_ws,RHSWorkspace=solid_angle)
         mantid.DeleteWorkspace(solid_angle)
     focused_vanadium = mantid.DiffractionFocussing(InputWorkspace=aligned_ws,
                                                    GroupingFileName=run_details.grouping_file_path)
-
+    # convert back to TOF based on engineered detector positions
+    mantid.ApplyDiffCal(InstrumentWorkspace=focused_vanadium,
+                        ClearCalibration=True)
     focused_spectra = common.extract_ws_spectra(focused_vanadium)
     focused_spectra = instrument._crop_van_to_expected_tof_range(focused_spectra)
 
     d_spacing_group, tof_group = instrument._output_focused_ws(processed_spectra=focused_spectra,
                                                                run_details=run_details)
-
     _create_vanadium_splines(focused_spectra, instrument, run_details)
 
     common.keep_single_ws_unit(d_spacing_group=d_spacing_group, tof_group=tof_group,
diff --git a/scripts/Diffraction/isis_powder/routines/focus.py b/scripts/Diffraction/isis_powder/routines/focus.py
index 51afbfc3258..0fb1cc8e293 100644
--- a/scripts/Diffraction/isis_powder/routines/focus.py
+++ b/scripts/Diffraction/isis_powder/routines/focus.py
@@ -65,8 +65,9 @@ def _focus_one_ws(input_workspace, run_number, instrument, perform_vanadium_norm
                              Geometry=common.generate_sample_geometry(sample_details),
                              Material=common.generate_sample_material(sample_details))
     # Align
-    aligned_ws = mantid.AlignDetectors(InputWorkspace=input_workspace,
-                                       CalibrationFile=run_details.offset_file_path)
+    mantid.ApplyDiffCal(InstrumentWorkspace=input_workspace,
+                        CalibrationFile=run_details.offset_file_path)
+    aligned_ws = mantid.ConvertUnits(InputWorkspace=input_workspace, Target="dSpacing")
 
     solid_angle = instrument.get_solid_angle_corrections(run_details.vanadium_run_numbers, run_details)
     if solid_angle:
@@ -77,6 +78,8 @@ def _focus_one_ws(input_workspace, run_number, instrument, perform_vanadium_norm
     focused_ws = mantid.DiffractionFocussing(InputWorkspace=aligned_ws,
                                              GroupingFileName=run_details.grouping_file_path)
 
+    instrument.apply_calibration_to_focused_data(focused_ws)
+
     calibrated_spectra = _apply_vanadium_corrections(instrument=instrument,
                                                      input_workspace=focused_ws,
                                                      perform_vanadium_norm=perform_vanadium_norm,
-- 
GitLab