From 8de22e320c8cd7787672ece160e4250c147dae2a Mon Sep 17 00:00:00 2001
From: Matthew Bowles <matthew.bowles@stfc.ac.uk>
Date: Tue, 12 Sep 2017 14:10:59 +0100
Subject: [PATCH] initial fit for single/multi ion/spectra Re #20358

---
 Framework/API/src/Algorithm.cpp               |   2 +-
 Framework/API/src/DetectorSearcher.cpp        |   2 +-
 Framework/API/src/IndexTypeProperty.cpp       |   2 +-
 Framework/API/src/MatrixWorkspace.cpp         |   2 +-
 .../ReflectometryReductionOne2.h              |   6 +
 Framework/Algorithms/src/AddSampleLog.cpp     |   6 +-
 Framework/Algorithms/src/ChangeTimeZero.cpp   |   2 +-
 .../Algorithms/src/ConvertAxisByFormula.cpp   |   8 +-
 Framework/Algorithms/src/ConvertUnits.cpp     |   2 +-
 .../DiffractionEventCalibrateDetectors.cpp    |   2 +-
 .../Algorithms/src/DiffractionFocussing2.cpp  |   2 +-
 .../Algorithms/src/ExportTimeSeriesLog.cpp    |   2 +-
 Framework/Algorithms/src/FilterByTime.cpp     |   4 +-
 Framework/Algorithms/src/FilterByTime2.cpp    |   4 +-
 Framework/Algorithms/src/FilterEvents.cpp     |   6 +-
 .../src/GenerateIPythonNotebook.cpp           |   4 +-
 .../Algorithms/src/GeneratePythonScript.cpp   |   4 +-
 Framework/Algorithms/src/Q1DWeighted.cpp      |   2 +-
 .../src/ReflectometryReductionOne2.cpp        | 132 +++-
 Framework/Algorithms/src/RenameWorkspaces.cpp |   6 +-
 Framework/Algorithms/src/ResampleX.cpp        |  32 +-
 Framework/Algorithms/src/Stitch1DMany.cpp     |   4 +-
 .../test/ReflectometryReductionOne2Test.h     | 112 ++--
 .../Crystal/inc/MantidCrystal/CentroidPeaks.h |   1 +
 Framework/Crystal/src/CentroidPeaks.cpp       |  59 +-
 Framework/Crystal/src/PeakHKLErrors.cpp       |   2 +-
 Framework/Crystal/src/SCDPanelErrors.cpp      |   2 +-
 Framework/Crystal/test/CentroidPeaksTest.h    |   8 +-
 .../src/Algorithms/ConvertToYSpace.cpp        |   2 +-
 Framework/CurveFitting/src/Algorithms/Fit.cpp |   2 +-
 .../src/Algorithms/PlotPeakByLogValue.cpp     |   2 +-
 .../src/Algorithms/SplineInterpolation.cpp    |   2 +-
 .../Functions/CrystalFieldMultiSpectrum.cpp   |   2 +-
 .../src/Functions/TabulatedFunction.cpp       |   2 +-
 .../DataHandling/src/DataBlockComposite.cpp   |   1 +
 .../DataHandling/src/DownloadInstrument.cpp   |   2 +-
 Framework/DataHandling/src/LoadAscii2.cpp     |   2 +-
 Framework/DataHandling/src/LoadFITS.cpp       |  18 +-
 .../src/LoadFullprofResolution.cpp            |   4 +-
 .../src/LoadGSASInstrumentFile.cpp            |   2 +-
 .../DataHandling/src/LoadIDFFromNexus.cpp     |  10 +-
 .../DataHandling/src/LoadILLDiffraction.cpp   |   2 +-
 .../DataHandling/src/LoadILLIndirect2.cpp     |   2 +-
 .../DataHandling/src/LoadILLReflectometry.cpp |   2 +-
 Framework/DataHandling/src/LoadILLSANS.cpp    |   2 +-
 Framework/DataHandling/src/LoadILLTOF2.cpp    |   2 +-
 Framework/DataHandling/src/LoadIsawDetCal.cpp |   2 +-
 Framework/DataHandling/src/LoadLLB.cpp        |   2 +-
 Framework/DataHandling/src/LoadMLZ.cpp        |   2 +-
 Framework/DataHandling/src/LoadSINQFocus.cpp  |   2 +-
 Framework/DataHandling/src/LoadSassena.cpp    |   1 +
 Framework/DataHandling/src/LoadSpice2D.cpp    |   1 +
 .../DataHandling/src/LoadSpiceXML2DDet.cpp    |   4 +-
 Framework/DataHandling/src/LoadTBL.cpp        |  16 +-
 Framework/DataHandling/src/RemoveLogs.cpp     |   1 +
 Framework/DataHandling/src/SaveAscii.cpp      |   2 +-
 Framework/DataHandling/src/SaveAscii2.cpp     |   2 +-
 .../DataHandling/src/SaveDiffFittingAscii.cpp |   1 +
 .../DataHandling/src/SaveReflCustomAscii.cpp  |   2 +-
 .../src/SaveReflThreeColumnAscii.cpp          |   2 +-
 .../src/SaveToSNSHistogramNexus.cpp           |   4 +-
 Framework/DataObjects/src/EventList.cpp       |   2 +-
 .../DataObjects/src/MDHistoWorkspace.cpp      |   1 +
 Framework/DataObjects/src/PeaksWorkspace.cpp  |  27 +-
 .../src/Crystal/ProductOfCyclicGroups.cpp     |   2 +-
 .../src/Crystal/SymmetryElementFactory.cpp    |   2 +-
 .../src/Crystal/SymmetryOperationFactory.cpp  |   1 +
 .../Instrument/InstrumentDefinitionParser.cpp |   2 +-
 .../src/MDGeometry/MDGeometryXMLParser.cpp    |  24 +-
 .../src/CountStandardDeviations.cpp           |   2 +-
 .../HistogramData/src/CountVariances.cpp      |   2 +-
 Framework/HistogramData/src/Counts.cpp        |   2 +-
 Framework/HistogramData/src/Frequencies.cpp   |   2 +-
 .../src/FrequencyStandardDeviations.cpp       |   2 +-
 .../HistogramData/src/FrequencyVariances.cpp  |   2 +-
 Framework/HistogramData/src/Histogram.cpp     |   9 +-
 Framework/HistogramData/src/Points.cpp        |   2 +-
 .../inc/MantidKernel/PropertyWithValue.tcc    |   2 +-
 Framework/Kernel/src/CompositeValidator.cpp   |   2 +-
 Framework/Kernel/src/ConfigService.cpp        |   4 +-
 Framework/Kernel/src/UsageService.cpp         |   2 +-
 .../MDAlgorithms/src/CompareMDWorkspaces.cpp  |   2 +-
 .../src/ConvertToDiffractionMDWorkspace3.cpp  |   2 +-
 .../MDAlgorithms/src/Integrate3DEvents.cpp    |  12 +-
 Framework/Nexus/src/NexusFileIO.cpp           |   2 +-
 .../PythonInterface/mantid/CMakeLists.txt     |   1 +
 .../PythonInterface/mantid/api/CMakeLists.txt |   1 +
 .../api/src/Exports/CompositeFunction.cpp     |  46 +-
 .../api/src/Exports/FunctionFactory.cpp       |  26 +
 .../mantid/api/src/Exports/IAlgorithm.cpp     |   2 +-
 .../mantid/api/src/Exports/IFunction.cpp      |  22 +
 .../api/src/Exports/MultiDomainFunction.cpp   |  24 +
 .../api/src/Exports/ProductFunction.cpp       |  48 ++
 .../PythonInterface/mantid/fitfunctions.py    | 523 ++++++++++++++++
 Framework/PythonInterface/mantid/simpleapi.py |  10 +-
 .../test/python/mantid/CMakeLists.txt         |   1 +
 .../test/python/mantid/FitFunctionsTest.py    | 581 ++++++++++++++++++
 .../test/python/mantid/SimpleAPIFitTest.py    |   8 +-
 Framework/SINQ/src/PoldiPeakSearch.cpp        |   2 +-
 .../TestHelpers/src/FileComparisonHelper.cpp  |   4 +-
 .../WorkflowAlgorithms/src/EQSANSLoad.cpp     |  39 +-
 .../tests/analysis/EnggCalibrationTest.py     |   4 +-
 .../tests/analysis/LoadLotsOfFiles.py         |   3 +-
 .../algorithms/AbsorptionCorrection-v1.rst    |  18 +-
 docs/source/concepts/FitFunctionsInPython.rst | 170 +++++
 docs/source/release/v3.11.0/diffraction.rst   |   2 +-
 docs/source/release/v3.11.0/framework.rst     |   2 +
 .../release/v3.11.0/indirect_inelastic.rst    |  15 +
 docs/source/release/v3.11.0/reflectometry.rst |   1 +
 docs/source/release/v3.11.0/sans.rst          |   5 +-
 .../AlignedThreeSliceFilter.cxx               |   2 +-
 .../vtkAlignedGeometrySliceRepresentation.cxx |  11 +-
 .../vtkAlignedGeometrySliceRepresentation.h   |  19 +-
 .../src/EventNexusLoadingPresenter.cpp        |   6 +-
 .../src/MDEWEventNexusLoadingPresenter.cpp    |   8 +-
 .../src/MDHWNexusLoadingPresenter.cpp         |   8 +-
 .../src/pqCameraReactionNonOrthogonalAxes.cpp |   2 +-
 .../src/pqCameraToolbarNonOrthogonalAxes.cpp  |   2 +-
 .../CrystalField/CrystalFieldMultiSite.py     |  85 ++-
 scripts/Inelastic/CrystalField/fitting.py     |   6 +-
 .../instruments/sans/sns_command_interface.py |   8 +
 scripts/test/CrystalFieldMultiSiteTest.py     | 119 +++-
 122 files changed, 2152 insertions(+), 319 deletions(-)
 create mode 100644 Framework/PythonInterface/mantid/api/src/Exports/MultiDomainFunction.cpp
 create mode 100644 Framework/PythonInterface/mantid/api/src/Exports/ProductFunction.cpp
 create mode 100644 Framework/PythonInterface/mantid/fitfunctions.py
 create mode 100644 Framework/PythonInterface/test/python/mantid/FitFunctionsTest.py
 create mode 100644 docs/source/concepts/FitFunctionsInPython.rst

diff --git a/Framework/API/src/Algorithm.cpp b/Framework/API/src/Algorithm.cpp
index 795addd969a..22239ea2e19 100644
--- a/Framework/API/src/Algorithm.cpp
+++ b/Framework/API/src/Algorithm.cpp
@@ -1711,7 +1711,7 @@ void Algorithm::execMasterOnly() {
  * non-master ranks in master-only execution. */
 void Algorithm::execNonMaster() {
   // If there is no output we can simply do nothing.
-  if (m_pureOutputWorkspaceProps.size() == 0)
+  if (m_pureOutputWorkspaceProps.empty())
     return;
   // Does Algorithm have exactly one input and one output workspace property?
   if (m_inputWorkspaceProps.size() == 1 &&
diff --git a/Framework/API/src/DetectorSearcher.cpp b/Framework/API/src/DetectorSearcher.cpp
index 76d8f93ebde..6170612c893 100644
--- a/Framework/API/src/DetectorSearcher.cpp
+++ b/Framework/API/src/DetectorSearcher.cpp
@@ -148,7 +148,7 @@ DetectorSearcher::searchUsingNearestNeighbours(const V3D &q) {
   // find where this Q vector should intersect with "extended" space
   const auto neighbours =
       m_detectorCacheSearch->findNearest(Eigen::Vector3d(q[0], q[1], q[2]), 5);
-  if (neighbours.size() == 0)
+  if (neighbours.empty())
     return std::make_tuple(false, 0);
 
   const auto result = checkInteceptWithNeighbours(detectorDir, neighbours);
diff --git a/Framework/API/src/IndexTypeProperty.cpp b/Framework/API/src/IndexTypeProperty.cpp
index df454100350..7a45c4741b7 100644
--- a/Framework/API/src/IndexTypeProperty.cpp
+++ b/Framework/API/src/IndexTypeProperty.cpp
@@ -10,7 +10,7 @@ IndexTypeProperty::IndexTypeProperty(const std::string &name,
   if (indexType & IndexType::SpectrumNum)
     m_allowedValues.push_back("SpectrumNumber");
 
-  if (m_allowedValues.size() == 0)
+  if (m_allowedValues.empty())
     throw std::invalid_argument("Argument indexType incorrectly specified");
 
   m_value = m_allowedValues[0];
diff --git a/Framework/API/src/MatrixWorkspace.cpp b/Framework/API/src/MatrixWorkspace.cpp
index 2a1794eb532..c07904a0494 100644
--- a/Framework/API/src/MatrixWorkspace.cpp
+++ b/Framework/API/src/MatrixWorkspace.cpp
@@ -239,7 +239,7 @@ void MatrixWorkspace::initialize(const std::size_t &NVectors,
 void MatrixWorkspace::initialize(const std::size_t &NVectors,
                                  const HistogramData::Histogram &histogram) {
   // Check validity of arguments
-  if (NVectors == 0 || histogram.x().size() == 0) {
+  if (NVectors == 0 || histogram.x().empty()) {
     throw std::out_of_range(
         "All arguments to init must be positive and non-zero");
   }
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/ReflectometryReductionOne2.h b/Framework/Algorithms/inc/MantidAlgorithms/ReflectometryReductionOne2.h
index 7c9aa809d16..49a8f680da0 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/ReflectometryReductionOne2.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/ReflectometryReductionOne2.h
@@ -8,6 +8,9 @@ namespace Mantid {
 namespace API {
 class SpectrumInfo;
 }
+namespace Geometry {
+class ReferenceFrame;
+}
 namespace HistogramData {
 class HistogramX;
 class HistogramY;
@@ -88,6 +91,8 @@ private:
   // convert to momentum transfer
   Mantid::API::MatrixWorkspace_sptr
   convertToQ(Mantid::API::MatrixWorkspace_sptr inputWS);
+  // Get the twoTheta width of a given detector
+  double getDetectorTwoThetaRange(const size_t spectrumIdx);
   // Utility function to create name for diagnostic workspaces
   std::string createDebugWorkspaceName(const std::string &inputName);
   // Utility function to output a diagnostic workspace to the ADS
@@ -152,6 +157,7 @@ private:
 
   API::MatrixWorkspace_sptr m_runWS;
   const API::SpectrumInfo *m_spectrumInfo;
+  boost::shared_ptr<const Mantid::Geometry::ReferenceFrame> m_refFrame;
   bool m_convertUnits;          // convert the input workspace to lambda
   bool m_normaliseMonitors;     // normalise by monitors and direct beam
   bool m_normaliseTransmission; // transmission or algorithmic correction
diff --git a/Framework/Algorithms/src/AddSampleLog.cpp b/Framework/Algorithms/src/AddSampleLog.cpp
index 5b7be49cd1e..7abbf956b7f 100644
--- a/Framework/Algorithms/src/AddSampleLog.cpp
+++ b/Framework/Algorithms/src/AddSampleLog.cpp
@@ -228,7 +228,7 @@ void AddSampleLog::addTimeSeriesProperty(Run &run_obj,
     is_int_series = true;
   } else if (prop_number_type == autoTypeOption) {
     // auto type. by default
-    if (prop_value.size() == 0)
+    if (prop_value.empty())
       g_log.warning("For sample log in TimeSeriesProperty and values are given "
                     "by MarixWorkspace, the default data type "
                     "is double.");
@@ -248,8 +248,8 @@ void AddSampleLog::addTimeSeriesProperty(Run &run_obj,
 
   // check using workspace or some specified start value
   std::string tsp_ws_name = getPropertyValue("TimeSeriesWorkspace");
-  bool use_ws = tsp_ws_name.size() > 0;
-  bool use_single_value = prop_value.size() > 0;
+  bool use_ws = !tsp_ws_name.empty();
+  bool use_single_value = !prop_value.empty();
   if (use_ws && use_single_value) {
     throw std::runtime_error("Both TimeSeries workspace and sing value are "
                              "specified.  It is not allowed.");
diff --git a/Framework/Algorithms/src/ChangeTimeZero.cpp b/Framework/Algorithms/src/ChangeTimeZero.cpp
index 631e7068f15..5494590af12 100644
--- a/Framework/Algorithms/src/ChangeTimeZero.cpp
+++ b/Framework/Algorithms/src/ChangeTimeZero.cpp
@@ -331,7 +331,7 @@ bool ChangeTimeZero::checkForDateTime(const std::string &val) const {
   // Hedge for bad lexical casts in the DateTimeValidator
   try {
     DateTimeValidator validator = DateTimeValidator();
-    isDateTime = validator.isValid(val) == "";
+    isDateTime = validator.isValid(val).empty();
   } catch (...) {
     isDateTime = false;
   }
diff --git a/Framework/Algorithms/src/ConvertAxisByFormula.cpp b/Framework/Algorithms/src/ConvertAxisByFormula.cpp
index ada62fb7d4e..de1ab2350a8 100644
--- a/Framework/Algorithms/src/ConvertAxisByFormula.cpp
+++ b/Framework/Algorithms/src/ConvertAxisByFormula.cpp
@@ -105,7 +105,7 @@ void ConvertAxisByFormula::exec() {
   RefAxis *refAxisPtr = dynamic_cast<RefAxis *>(axisPtr);
   if (refAxisPtr != nullptr) {
     CommonBinsValidator sameBins;
-    if (sameBins.isValid(outputWs) != "") {
+    if (!sameBins.isValid(outputWs).empty()) {
       isRaggedBins = true;
     }
     isRefAxis = true;
@@ -245,14 +245,14 @@ void ConvertAxisByFormula::exec() {
   }
 
   // Set the Unit of the Axis
-  if ((axisUnits != "") || (axisTitle != "")) {
+  if ((!axisUnits.empty()) || (!axisTitle.empty())) {
     try {
       axisPtr->unit() = UnitFactory::Instance().create(axisUnits);
     } catch (Exception::NotFoundError &) {
-      if (axisTitle == "") {
+      if (axisTitle.empty()) {
         axisTitle = axisPtr->unit()->caption();
       }
-      if (axisUnits == "") {
+      if (axisUnits.empty()) {
         axisUnits = axisPtr->unit()->label();
       }
       axisPtr->unit() = boost::make_shared<Units::Label>(axisTitle, axisUnits);
diff --git a/Framework/Algorithms/src/ConvertUnits.cpp b/Framework/Algorithms/src/ConvertUnits.cpp
index 33fa5a87287..7f58d71c62d 100644
--- a/Framework/Algorithms/src/ConvertUnits.cpp
+++ b/Framework/Algorithms/src/ConvertUnits.cpp
@@ -337,7 +337,7 @@ ConvertUnits::convertQuickly(API::MatrixWorkspace_const_sptr inputWS,
   // First a quick check using the validator
   CommonBinsValidator sameBins;
   bool commonBoundaries = false;
-  if (sameBins.isValid(inputWS) == "") {
+  if (sameBins.isValid(inputWS).empty()) {
     commonBoundaries = WorkspaceHelpers::commonBoundaries(*inputWS);
     // Only do the full check if the quick one passes
     if (commonBoundaries) {
diff --git a/Framework/Algorithms/src/DiffractionEventCalibrateDetectors.cpp b/Framework/Algorithms/src/DiffractionEventCalibrateDetectors.cpp
index 5136276a2e8..24381be1b96 100644
--- a/Framework/Algorithms/src/DiffractionEventCalibrateDetectors.cpp
+++ b/Framework/Algorithms/src/DiffractionEventCalibrateDetectors.cpp
@@ -299,7 +299,7 @@ void DiffractionEventCalibrateDetectors::exec() {
   std::vector<boost::shared_ptr<RectangularDetector>> detList;
   // --------- Loading only one bank ----------------------------------
   std::string onebank = getProperty("BankName");
-  bool doOneBank = (onebank != "");
+  bool doOneBank = (!onebank.empty());
   for (int i = 0; i < inst->nelements(); i++) {
     boost::shared_ptr<RectangularDetector> det;
     boost::shared_ptr<ICompAssembly> assem;
diff --git a/Framework/Algorithms/src/DiffractionFocussing2.cpp b/Framework/Algorithms/src/DiffractionFocussing2.cpp
index 5465caa11d9..875954fbee9 100644
--- a/Framework/Algorithms/src/DiffractionFocussing2.cpp
+++ b/Framework/Algorithms/src/DiffractionFocussing2.cpp
@@ -109,7 +109,7 @@ void DiffractionFocussing2::exec() {
     throw std::invalid_argument("Workspace Invalid Spacing/UnitID");
   }
   // --- Do we need to read the grouping workspace? ----
-  if (groupingFileName != "") {
+  if (!groupingFileName.empty()) {
     progress(0.01, "Reading grouping file");
     IAlgorithm_sptr childAlg = createChildAlgorithm("CreateGroupingWorkspace");
     childAlg->setProperty(
diff --git a/Framework/Algorithms/src/ExportTimeSeriesLog.cpp b/Framework/Algorithms/src/ExportTimeSeriesLog.cpp
index bdce22f34a0..35c15bc9fa4 100644
--- a/Framework/Algorithms/src/ExportTimeSeriesLog.cpp
+++ b/Framework/Algorithms/src/ExportTimeSeriesLog.cpp
@@ -429,7 +429,7 @@ void ExportTimeSeriesLog::calculateFirstDerivative(bool is_event_ws) {
 
   // error message
   std::string errmsg = errmsg_ss.str();
-  if (errmsg.size() > 0)
+  if (!errmsg.empty())
     g_log.error(errmsg);
 
   return;
diff --git a/Framework/Algorithms/src/FilterByTime.cpp b/Framework/Algorithms/src/FilterByTime.cpp
index dd08a9f46bd..fe151e505ff 100644
--- a/Framework/Algorithms/src/FilterByTime.cpp
+++ b/Framework/Algorithms/src/FilterByTime.cpp
@@ -77,12 +77,12 @@ void FilterByTime::exec() {
   start_str = getPropertyValue("AbsoluteStartTime");
   stop_str = getPropertyValue("AbsoluteStopTime");
 
-  if ((start_str != "") && (stop_str != "") && (start_dbl <= 0.0) &&
+  if ((!start_str.empty()) && (!stop_str.empty()) && (start_dbl <= 0.0) &&
       (stop_dbl <= 0.0)) {
     // Use the absolute string
     start = DateAndTime(start_str);
     stop = DateAndTime(stop_str);
-  } else if ((start_str == "") && (stop_str == "") &&
+  } else if ((start_str.empty()) && (stop_str.empty()) &&
              ((start_dbl > 0.0) || (stop_dbl > 0.0))) {
     // Use the relative times in seconds.
     DateAndTime first = inputWS->getFirstPulseTime();
diff --git a/Framework/Algorithms/src/FilterByTime2.cpp b/Framework/Algorithms/src/FilterByTime2.cpp
index 74ab5708084..71d70f36280 100644
--- a/Framework/Algorithms/src/FilterByTime2.cpp
+++ b/Framework/Algorithms/src/FilterByTime2.cpp
@@ -73,12 +73,12 @@ void FilterByTime2::exec() {
   std::string absstoptime = this->getProperty("AbsoluteStopTime");
 
   std::string start, stop;
-  if ((absstarttime != "") && (absstoptime != "") && (starttime <= 0.0) &&
+  if ((!absstarttime.empty()) && (!absstoptime.empty()) && (starttime <= 0.0) &&
       (stoptime <= 0.0)) {
     // Use the absolute string
     start = absstarttime;
     stop = absstoptime;
-  } else if ((absstarttime != "" || absstoptime != "") &&
+  } else if ((!absstarttime.empty() || !absstoptime.empty()) &&
              (starttime > 0.0 || stoptime > 0.0)) {
     throw std::invalid_argument(
         "It is not allowed to provide both absolute time and relative time.");
diff --git a/Framework/Algorithms/src/FilterEvents.cpp b/Framework/Algorithms/src/FilterEvents.cpp
index 9f6b05f04ff..c4a4cf3116e 100644
--- a/Framework/Algorithms/src/FilterEvents.cpp
+++ b/Framework/Algorithms/src/FilterEvents.cpp
@@ -792,7 +792,7 @@ void FilterEvents::convertSplittersWorkspaceToVectors() {
     Kernel::SplittingInterval splitter = m_splitters[i_splitter];
     int64_t start_time_i64 = splitter.start().totalNanoseconds();
     int64_t stop_time_i64 = splitter.stop().totalNanoseconds();
-    if (m_vecSplitterTime.size() == 0) {
+    if (m_vecSplitterTime.empty()) {
       // first entry: add
       m_vecSplitterTime.push_back(start_time_i64);
       m_vecSplitterTime.push_back(stop_time_i64);
@@ -949,7 +949,7 @@ void FilterEvents::processTableSplittersWorkspace() {
     int64_t stop_64 =
         filter_shift_time + static_cast<int64_t>(stop_time * 1.E9);
 
-    if (m_vecSplitterTime.size() == 0) {
+    if (m_vecSplitterTime.empty()) {
       // first splitter: push the start time to vector
       m_vecSplitterTime.push_back(start_64);
     } else if (start_64 - m_vecSplitterTime.back() > TOLERANCE) {
@@ -973,7 +973,7 @@ void FilterEvents::processTableSplittersWorkspace() {
     // convert string-target to integer target
     bool addnew = false;
     int int_target(-1);
-    if (m_targetIndexMap.size() == 0) {
+    if (m_targetIndexMap.empty()) {
       addnew = true;
     } else {
       std::map<std::string, int>::iterator mapiter =
diff --git a/Framework/Algorithms/src/GenerateIPythonNotebook.cpp b/Framework/Algorithms/src/GenerateIPythonNotebook.cpp
index 529b3354907..e5e90cd9a25 100644
--- a/Framework/Algorithms/src/GenerateIPythonNotebook.cpp
+++ b/Framework/Algorithms/src/GenerateIPythonNotebook.cpp
@@ -80,8 +80,8 @@ void GenerateIPythonNotebook::exec() {
   }
 
   // Need at least a start time to do time filter
-  if (startTime != "") {
-    if (endTime == "") {
+  if (!startTime.empty()) {
+    if (endTime.empty()) {
       // If no end time was given then filter up to now
       view->filterBetweenExecDate(DateAndTime(startTime));
     } else {
diff --git a/Framework/Algorithms/src/GeneratePythonScript.cpp b/Framework/Algorithms/src/GeneratePythonScript.cpp
index 5ee5894becf..01ee8704fcf 100644
--- a/Framework/Algorithms/src/GeneratePythonScript.cpp
+++ b/Framework/Algorithms/src/GeneratePythonScript.cpp
@@ -79,8 +79,8 @@ void GeneratePythonScript::exec() {
   }
 
   // Need at least a start time to do time filter
-  if (startTime != "") {
-    if (endTime == "") {
+  if (!startTime.empty()) {
+    if (endTime.empty()) {
       // If no end time was given then filter up to now
       view->filterBetweenExecDate(DateAndTime(startTime));
     } else {
diff --git a/Framework/Algorithms/src/Q1DWeighted.cpp b/Framework/Algorithms/src/Q1DWeighted.cpp
index 36fc5177d2c..d12104bab2f 100644
--- a/Framework/Algorithms/src/Q1DWeighted.cpp
+++ b/Framework/Algorithms/src/Q1DWeighted.cpp
@@ -213,7 +213,7 @@ void Q1DWeighted::exec() {
         // For reference - in the case where we don't use sub-pixels, simply
         // use:
         //     double sinTheta = sin( spectrumInfo.twoTheta(i)/2.0 );
-        V3D pos = spectrumInfo.position(i) - V3D(sub_x, sub_y, 0.0);
+        V3D pos = spectrumInfo.position(i) - V3D(sub_x, sub_y, 0.0) - samplePos;
         double sinTheta = sin(0.5 * pos.angle(beamLine));
         double factor = fmp * sinTheta;
         double q = factor * 2.0 / (XIn[j] + XIn[j + 1]);
diff --git a/Framework/Algorithms/src/ReflectometryReductionOne2.cpp b/Framework/Algorithms/src/ReflectometryReductionOne2.cpp
index 181ab0a98af..e6c3288f561 100644
--- a/Framework/Algorithms/src/ReflectometryReductionOne2.cpp
+++ b/Framework/Algorithms/src/ReflectometryReductionOne2.cpp
@@ -4,18 +4,22 @@
 #include "MantidAPI/SpectrumInfo.h"
 #include "MantidAPI/MatrixWorkspace.h"
 #include "MantidAPI/WorkspaceFactory.h"
+#include "MantidGeometry/Objects/BoundingBox.h"
 #include "MantidHistogramData/LinearGenerator.h"
 #include "MantidIndexing/IndexInfo.h"
 #include "MantidKernel/MandatoryValidator.h"
 #include "MantidKernel/StringTokenizer.h"
 #include "MantidKernel/Unit.h"
 #include "MantidGeometry/IDetector.h"
+#include "MantidGeometry/Instrument.h"
+#include "MantidGeometry/Instrument/ReferenceFrame.h"
 
 #include <algorithm>
 #include <boost/lexical_cast.hpp>
 
 using namespace Mantid::Kernel;
 using namespace Mantid::API;
+using namespace Mantid::Geometry;
 using namespace Mantid::HistogramData;
 using namespace Mantid::Indexing;
 
@@ -37,27 +41,6 @@ double getDetectorTwoTheta(const SpectrumInfo *spectrumInfo,
   return spectrumInfo->signedTwoTheta(spectrumIdx);
 }
 
-/** Get the twoTheta angle range for the top/bottom of the detector associated
-* with the given spectrum
-*
-* @param spectrumInfo : the spectrum info
-* @param spectrumIdx : the workspace index of the spectrum
-* @return : the twoTheta angle in radians
-*/
-double getDetectorTwoThetaRange(const SpectrumInfo *spectrumInfo,
-                                const size_t spectrumIdx) {
-  // Assume the range covered by this pixel is the diff between this
-  // pixel's twoTheta and the next/prev pixel)
-  double twoTheta = getDetectorTwoTheta(spectrumInfo, spectrumIdx);
-  double bTwoTheta = 0;
-
-  if (spectrumIdx + 1 < spectrumInfo->size()) {
-    bTwoTheta = getDetectorTwoTheta(spectrumInfo, spectrumIdx + 1) - twoTheta;
-  }
-
-  return bTwoTheta;
-}
-
 /** Get the start/end of the lambda range for the detector associated
 * with the given spectrum
 *
@@ -328,6 +311,34 @@ std::string createProcessingCommandsFromDetectorWS(
 
   return result;
 }
+
+/**
+* Get the topbottom extent of a detector for the given axis
+*
+* @param axis [in] : the axis to get the extent for
+* @param top [in] : if true, get the max extent, or min otherwise
+* @return : the max/min extent on the given axis
+*/
+double getBoundingBoxExtent(const BoundingBox &boundingBox,
+                            const PointingAlong axis, const bool top) {
+
+  double result = 0.0;
+  switch (axis) {
+  case X:
+    result = top ? boundingBox.xMax() : boundingBox.xMin();
+    break;
+  case Y:
+    result = top ? boundingBox.yMax() : boundingBox.yMin();
+    break;
+  case Z:
+    result = top ? boundingBox.zMax() : boundingBox.zMin();
+    break;
+  default:
+    throw std::runtime_error("Axis is not X/Y/Z");
+    break;
+  }
+  return result;
+}
 }
 
 // Register the algorithm into the AlgorithmFactory
@@ -434,7 +445,10 @@ void ReflectometryReductionOne2::exec() {
     throw std::invalid_argument(
         "InputWorkspace must have units of TOF or Wavelength");
 
+  // Cache the spectrum info and reference frame
   m_spectrumInfo = &m_runWS->spectrumInfo();
+  auto instrument = m_runWS->getInstrument();
+  m_refFrame = instrument->getReferenceFrame();
 
   // Find and cache detector groups and theta0
   findDetectorGroups();
@@ -463,6 +477,44 @@ void ReflectometryReductionOne2::exec() {
   setProperty("OutputWorkspace", IvsQ);
 }
 
+/** Get the twoTheta angle range for the top/bottom of the detector associated
+* with the given spectrum
+*
+* @param spectrumIdx : the workspace index of the spectrum
+* @return : the twoTheta range in radians
+*/
+double
+ReflectometryReductionOne2::getDetectorTwoThetaRange(const size_t spectrumIdx) {
+
+  double bTwoTheta = 0;
+
+  // Get the sample->detector distance along the beam
+  const V3D detSample =
+      m_spectrumInfo->position(spectrumIdx) - m_spectrumInfo->samplePosition();
+  const double beamOffset =
+      m_refFrame->vecPointingAlongBeam().scalar_prod(detSample);
+  // Get the bounding box for this detector/group
+  BoundingBox boundingBox;
+  auto detector = m_runWS->getDetector(spectrumIdx);
+  detector->getBoundingBox(boundingBox);
+  // Get the top and bottom on the axis pointing up
+  const double top =
+      getBoundingBoxExtent(boundingBox, m_refFrame->pointingUp(), true);
+  const double bottom =
+      getBoundingBoxExtent(boundingBox, m_refFrame->pointingUp(), false);
+  // Calculate the difference in twoTheta between the top and bottom
+  const double twoThetaTop = std::atan(top / beamOffset);
+  const double twoThetaBottom = std::atan(bottom / beamOffset);
+  bTwoTheta = twoThetaTop - twoThetaBottom;
+
+  // We must have non-zero width to project a range
+  if (bTwoTheta < Tolerance) {
+    throw std::runtime_error("Error calculating pixel size.");
+  }
+
+  return bTwoTheta;
+}
+
 /**
 * Utility function to create a unique workspace name for diagnostic outputs
 * based on a given input workspace name
@@ -856,9 +908,19 @@ void ReflectometryReductionOne2::findDetectorGroups() {
               return a.front() < b.front();
             });
 
-  if (m_detectorGroups.size() == 0) {
+  if (m_detectorGroups.empty()) {
     throw std::runtime_error("Invalid processing instructions");
   }
+
+  for (const auto &group : m_detectorGroups) {
+    for (const auto &spIdx : group) {
+      if (spIdx > m_spectrumInfo->size() - 1) {
+        throw std::runtime_error(
+            "ProcessingInstructions contains an out-of-range index: " +
+            std::to_string(spIdx));
+      }
+    }
+  }
 }
 
 /**
@@ -993,8 +1055,7 @@ void ReflectometryReductionOne2::findIvsLamRange(
 
   const size_t spIdxMin = detectors.front();
   const double twoThetaMin = getDetectorTwoTheta(m_spectrumInfo, spIdxMin);
-  const double bTwoThetaMin =
-      getDetectorTwoThetaRange(m_spectrumInfo, spIdxMin);
+  const double bTwoThetaMin = getDetectorTwoThetaRange(spIdxMin);
   // For bLambda, use the average bin size for this spectrum
   auto xValues = detectorWS->x(spIdxMin);
   double bLambda = (xValues[xValues.size() - 1] - xValues[0]) /
@@ -1004,8 +1065,7 @@ void ReflectometryReductionOne2::findIvsLamRange(
 
   const size_t spIdxMax = detectors.back();
   const double twoThetaMax = getDetectorTwoTheta(m_spectrumInfo, spIdxMax);
-  const double bTwoThetaMax =
-      getDetectorTwoThetaRange(m_spectrumInfo, spIdxMax);
+  const double bTwoThetaMax = getDetectorTwoThetaRange(spIdxMax);
   xValues = detectorWS->x(spIdxMax);
   bLambda = (xValues[xValues.size() - 1] - xValues[0]) /
             static_cast<int>(xValues.size());
@@ -1092,7 +1152,7 @@ ReflectometryReductionOne2::sumInQ(MatrixWorkspace_sptr detectorWS) {
     for (auto spIdx : detectors) {
       // Get the angle of this detector and its size in twoTheta
       const double twoTheta = getDetectorTwoTheta(m_spectrumInfo, spIdx);
-      const double bTwoTheta = getDetectorTwoThetaRange(m_spectrumInfo, spIdx);
+      const double bTwoTheta = getDetectorTwoThetaRange(spIdx);
 
       // Check X length is Y length + 1
       const auto &inputX = detectorWS->x(spIdx);
@@ -1228,11 +1288,19 @@ void ReflectometryReductionOne2::sumInQShareCounts(
     }
     // Add a share of the input counts to this bin based on the proportion of
     // overlap.
-    const double overlapWidth =
-        std::min({bLambda, lambdaMax - binStart, binEnd - lambdaMin});
-    const double fraction = overlapWidth / totalWidth;
-    outputY[outIdx] += inputCounts * fraction;
-    outputE[outIdx] += inputErr * fraction;
+    if (totalWidth > Tolerance) {
+      // Share counts out proportionally based on the overlap of this range
+      const double overlapWidth =
+          std::min({bLambda, lambdaMax - binStart, binEnd - lambdaMin});
+      const double fraction = overlapWidth / totalWidth;
+      outputY[outIdx] += inputCounts * fraction;
+      outputE[outIdx] += inputErr * fraction;
+    } else {
+      // Projection to a single value. Put all counts in the overlapping output
+      // bin.
+      outputY[outIdx] += inputCounts;
+      outputE[outIdx] += inputCounts;
+    }
   }
 }
 
diff --git a/Framework/Algorithms/src/RenameWorkspaces.cpp b/Framework/Algorithms/src/RenameWorkspaces.cpp
index 9a6855d499b..7af32cd87e4 100644
--- a/Framework/Algorithms/src/RenameWorkspaces.cpp
+++ b/Framework/Algorithms/src/RenameWorkspaces.cpp
@@ -61,15 +61,15 @@ std::map<std::string, std::string> RenameWorkspaces::validateInputs() {
   std::string suffix = getPropertyValue("Suffix");
 
   // Check properties
-  if (newWsName.empty() && prefix == "" && suffix == "") {
+  if (newWsName.empty() && prefix.empty() && suffix.empty()) {
     errorList["WorkspaceNames"] =
         "No list of Workspace names, prefix or suffix has been supplied.";
   }
 
-  if (!newWsName.empty() && (prefix != "" || suffix != "")) {
+  if (!newWsName.empty() && (!prefix.empty() || !suffix.empty())) {
     errorList["WorkspaceNames"] = "Both a list of workspace names and a prefix "
                                   "or suffix has been supplied.";
-    if (prefix != "") {
+    if (!prefix.empty()) {
       errorList["Prefix"] = "Both a list of workspace names and a prefix "
                             "or suffix has been supplied.";
     } else {
diff --git a/Framework/Algorithms/src/ResampleX.cpp b/Framework/Algorithms/src/ResampleX.cpp
index 42d065d13e0..82c462f4fcd 100644
--- a/Framework/Algorithms/src/ResampleX.cpp
+++ b/Framework/Algorithms/src/ResampleX.cpp
@@ -114,8 +114,21 @@ map<string, string> ResampleX::validateInputs() {
  */
 string determineXMinMax(MatrixWorkspace_sptr inputWS, vector<double> &xmins,
                         vector<double> &xmaxs) {
-  bool updateXMins = xmins.empty(); // they weren't set
-  bool updateXMaxs = xmaxs.empty(); // they weren't set
+  const size_t numSpectra = inputWS->getNumberHistograms();
+
+  // pad out the ranges by copying the first value to the rest that are needed
+  if (xmins.size() == 1 && numSpectra > xmins.size()) {
+    const double value = xmins.front();
+    xmins.insert(xmins.end(), numSpectra - xmins.size(), value);
+  }
+  if (xmaxs.size() == 1 && numSpectra > xmaxs.size()) {
+    const double value = xmaxs.front();
+    xmaxs.insert(xmaxs.end(), numSpectra - xmaxs.size(), value);
+  }
+
+  // should the individiual values be calculated?
+  const bool updateXMins = xmins.empty(); // they weren't set
+  const bool updateXMaxs = xmaxs.empty(); // they weren't set
 
   stringstream msg;
 
@@ -129,7 +142,6 @@ string determineXMinMax(MatrixWorkspace_sptr inputWS, vector<double> &xmins,
     xmax_wksp = inputEventWS->getTofMax();
   }
 
-  size_t numSpectra = inputWS->getNumberHistograms();
   for (size_t i = 0; i < numSpectra; ++i) {
     // determine ranges if necessary
     if (updateXMins || updateXMaxs) {
@@ -283,7 +295,7 @@ void ResampleX::exec() {
   bool inPlace = (inputWS == outputWS); // Rebinning in-place
   m_isDistribution = inputWS->isDistribution();
   m_isHistogram = inputWS->isHistogramData();
-  int numSpectra = static_cast<int>(inputWS->getNumberHistograms());
+  const int numSpectra = static_cast<int>(inputWS->getNumberHistograms());
 
   // the easy parameters
   m_useLogBinning = getProperty("LogBinning");
@@ -335,8 +347,8 @@ void ResampleX::exec() {
       if (common_limits) {
         // get the delta from the first since they are all the same
         BinEdges xValues(0);
-        double delta = this->determineBinning(xValues.mutableRawData(),
-                                              xmins[0], xmaxs[0]);
+        const double delta = this->determineBinning(xValues.mutableRawData(),
+                                                    xmins[0], xmaxs[0]);
         g_log.debug() << "delta = " << delta << "\n";
         outputEventWS->setAllX(xValues);
       } else {
@@ -348,7 +360,7 @@ void ResampleX::exec() {
         for (int wkspIndex = 0; wkspIndex < numSpectra; ++wkspIndex) {
           PARALLEL_START_INTERUPT_REGION
           BinEdges xValues(0);
-          double delta = this->determineBinning(
+          const double delta = this->determineBinning(
               xValues.mutableRawData(), xmins[wkspIndex], xmaxs[wkspIndex]);
           g_log.debug() << "delta[wkspindex=" << wkspIndex << "] = " << delta
                         << " xmin=" << xmins[wkspIndex]
@@ -378,7 +390,7 @@ void ResampleX::exec() {
 
         // Set the X axis for each output histogram
         MantidVec xValues;
-        double delta =
+        const double delta =
             this->determineBinning(xValues, xmins[wkspIndex], xmaxs[wkspIndex]);
         g_log.debug() << "delta[wkspindex=" << wkspIndex << "] = " << delta
                       << "\n";
@@ -458,8 +470,8 @@ void ResampleX::exec() {
 
       // create new output X axis
       MantidVec XValues_new;
-      double delta = this->determineBinning(XValues_new, xmins[wkspIndex],
-                                            xmaxs[wkspIndex]);
+      const double delta = this->determineBinning(XValues_new, xmins[wkspIndex],
+                                                  xmaxs[wkspIndex]);
       g_log.debug() << "delta[wkspindex=" << wkspIndex << "] = " << delta
                     << "\n";
 
diff --git a/Framework/Algorithms/src/Stitch1DMany.cpp b/Framework/Algorithms/src/Stitch1DMany.cpp
index afb9e4feefd..77f76abccba 100644
--- a/Framework/Algorithms/src/Stitch1DMany.cpp
+++ b/Framework/Algorithms/src/Stitch1DMany.cpp
@@ -205,7 +205,7 @@ void Stitch1DMany::validateGroupWorkspacesInputs() {
 
   // Log all errors and throw a runtime error if an error is found
   validateCommonInputs(errors);
-  if (errors.size() > 0) {
+  if (!errors.empty()) {
     auto &warnLog = getLogger().warning();
     for (const auto &error : errors) {
       warnLog << "Invalid value for " << error.first << ": " << error.second
@@ -241,7 +241,7 @@ void Stitch1DMany::validateCommonInputs(
   m_useManualScaleFactors = this->getProperty("UseManualScaleFactors");
   m_manualScaleFactors = this->getProperty("ManualScaleFactors");
 
-  if (m_manualScaleFactors.size() > 0) {
+  if (!m_manualScaleFactors.empty()) {
     if (m_manualScaleFactors.size() == 1) {
       // Single value: fill with list of the same scale factor value
       m_manualScaleFactors = std::vector<double>(numStitchableWS - 1,
diff --git a/Framework/Algorithms/test/ReflectometryReductionOne2Test.h b/Framework/Algorithms/test/ReflectometryReductionOne2Test.h
index a81db535a25..66c6b76ee50 100644
--- a/Framework/Algorithms/test/ReflectometryReductionOne2Test.h
+++ b/Framework/Algorithms/test/ReflectometryReductionOne2Test.h
@@ -18,6 +18,7 @@ using namespace WorkspaceCreationHelper;
 
 class ReflectometryReductionOne2Test : public CxxTest::TestSuite {
 private:
+  MatrixWorkspace_sptr m_singleDetectorWS;
   MatrixWorkspace_sptr m_multiDetectorWS;
   MatrixWorkspace_sptr m_transmissionWS;
 
@@ -33,6 +34,8 @@ public:
 
   ReflectometryReductionOne2Test() {
     FrameworkManager::Instance();
+    // A single detector ws
+    m_singleDetectorWS = create2DWorkspaceWithReflectometryInstrument(0);
     // A multi detector ws
     m_multiDetectorWS =
         create2DWorkspaceWithReflectometryInstrumentMultiDetector(0, 0.1);
@@ -447,12 +450,12 @@ public:
     alg.setProperty("ThetaIn", 25.0);
     MatrixWorkspace_sptr outLam = runAlgorithmLam(alg, 12);
 
-    TS_ASSERT_DELTA(outLam->x(0)[0], 0.927132, 1e-6);
-    TS_ASSERT_DELTA(outLam->x(0)[3], 5.165740, 1e-6);
-    TS_ASSERT_DELTA(outLam->x(0)[7], 10.817217, 1e-6);
-    TS_ASSERT_DELTA(outLam->y(0)[0], 2.773699, 1e-6);
-    TS_ASSERT_DELTA(outLam->y(0)[3], 2.828460, 1e-6);
-    TS_ASSERT_DELTA(outLam->y(0)[7], 2.816935, 1e-6);
+    TS_ASSERT_DELTA(outLam->x(0)[0], 0.934991, 1e-6);
+    TS_ASSERT_DELTA(outLam->x(0)[3], 5.173599, 1e-6);
+    TS_ASSERT_DELTA(outLam->x(0)[7], 10.825076, 1e-6);
+    TS_ASSERT_DELTA(outLam->y(0)[0], 2.768185, 1e-6);
+    TS_ASSERT_DELTA(outLam->y(0)[3], 2.792649, 1e-6);
+    TS_ASSERT_DELTA(outLam->y(0)[7], 2.787410, 1e-6);
   }
 
   void test_sum_in_q_non_flat_sample() {
@@ -469,12 +472,12 @@ public:
     alg.setProperty("ReductionType", "NonFlatSample");
     MatrixWorkspace_sptr outLam = runAlgorithmLam(alg, 10);
 
-    TS_ASSERT_DELTA(outLam->x(0)[0], 0.822974, 1e-6);
-    TS_ASSERT_DELTA(outLam->x(0)[3], 5.061582, 1e-6);
-    TS_ASSERT_DELTA(outLam->x(0)[7], 10.713059, 1e-6);
-    TS_ASSERT_DELTA(outLam->y(0)[0], 3.140302, 1e-6);
-    TS_ASSERT_DELTA(outLam->y(0)[3], 3.140457, 1e-6);
-    TS_ASSERT_DELTA(outLam->y(0)[7], 3.140644, 1e-6);
+    TS_ASSERT_DELTA(outLam->x(0)[0], 0.825488, 1e-6);
+    TS_ASSERT_DELTA(outLam->x(0)[3], 5.064095, 1e-6);
+    TS_ASSERT_DELTA(outLam->x(0)[7], 10.715573, 1e-6);
+    TS_ASSERT_DELTA(outLam->y(0)[0], 3.141858, 1e-6);
+    TS_ASSERT_DELTA(outLam->y(0)[3], 3.141885, 1e-6);
+    TS_ASSERT_DELTA(outLam->y(0)[7], 3.141920, 1e-6);
   }
 
   void test_sum_in_q_direct_beam() {
@@ -492,12 +495,12 @@ public:
     alg.setProperty("ThetaIn", 25.0);
     MatrixWorkspace_sptr outLam = runAlgorithmLam(alg, 11);
 
-    TS_ASSERT_DELTA(outLam->x(0)[0], 0.913144, 1e-6);
-    TS_ASSERT_DELTA(outLam->x(0)[3], 5.151752, 1e-6);
-    TS_ASSERT_DELTA(outLam->x(0)[7], 10.803229, 1e-6);
-    TS_ASSERT_DELTA(outLam->y(0)[0], 0.447237, 1e-6);
-    TS_ASSERT_DELTA(outLam->y(0)[3], 0.454605, 1e-6);
-    TS_ASSERT_DELTA(outLam->y(0)[7], 0.451946, 1e-6);
+    TS_ASSERT_DELTA(outLam->x(0)[0], 0.920496, 1e-6);
+    TS_ASSERT_DELTA(outLam->x(0)[3], 5.159104, 1e-6);
+    TS_ASSERT_DELTA(outLam->x(0)[7], 10.810581, 1e-6);
+    TS_ASSERT_DELTA(outLam->y(0)[0], 0.446571, 1e-6);
+    TS_ASSERT_DELTA(outLam->y(0)[3], 0.449843, 1e-6);
+    TS_ASSERT_DELTA(outLam->y(0)[7], 0.448591, 1e-6);
   }
 
   void test_sum_in_q_monitor_normalization() {
@@ -527,12 +530,12 @@ public:
     alg.setProperty("ThetaIn", 25.0);
     MatrixWorkspace_sptr outLam = runAlgorithmLam(alg, 13);
 
-    TS_ASSERT_DELTA(outLam->x(0)[0], -0.742692, 1e-6);
-    TS_ASSERT_DELTA(outLam->x(0)[5], 6.321654, 1e-6);
-    TS_ASSERT_DELTA(outLam->x(0)[9], 11.973131, 1e-6);
-    TS_ASSERT_DELTA(outLam->y(0)[0], 5.044175, 1e-6);
-    TS_ASSERT_DELTA(outLam->y(0)[5], 2.118472, 1e-6);
-    TS_ASSERT_DELTA(outLam->y(0)[9], 2.280546, 1e-6);
+    TS_ASSERT_DELTA(outLam->x(0)[0], -0.748671, 1e-6);
+    TS_ASSERT_DELTA(outLam->x(0)[5], 6.315674, 1e-6);
+    TS_ASSERT_DELTA(outLam->x(0)[9], 11.967151, 1e-6);
+    TS_ASSERT_DELTA(outLam->y(0)[0], 5.040302, 1e-6);
+    TS_ASSERT_DELTA(outLam->y(0)[5], 2.193649, 1e-6);
+    TS_ASSERT_DELTA(outLam->y(0)[9], 2.255101, 1e-6);
   }
 
   void test_sum_in_q_transmission_correction_run() {
@@ -546,12 +549,12 @@ public:
     alg.setProperty("ThetaIn", 25.0);
     MatrixWorkspace_sptr outLam = runAlgorithmLam(alg, 12);
 
-    TS_ASSERT_DELTA(outLam->x(0)[0], 0.927132, 1e-6);
-    TS_ASSERT_DELTA(outLam->x(0)[3], 5.165740, 1e-6);
-    TS_ASSERT_DELTA(outLam->x(0)[7], 10.817217, 1e-6);
-    TS_ASSERT_DELTA(outLam->y(0)[0], 0.620714, 1e-6);
-    TS_ASSERT_DELTA(outLam->y(0)[3], 0.899935, 1e-6);
-    TS_ASSERT_DELTA(outLam->y(0)[7], 0.896268, 1e-6);
+    TS_ASSERT_DELTA(outLam->x(0)[0], 0.934991, 1e-6);
+    TS_ASSERT_DELTA(outLam->x(0)[3], 5.173599, 1e-6);
+    TS_ASSERT_DELTA(outLam->x(0)[7], 10.825076, 1e-6);
+    TS_ASSERT_DELTA(outLam->y(0)[0], 0.631775, 1e-6);
+    TS_ASSERT_DELTA(outLam->y(0)[3], 0.888541, 1e-6);
+    TS_ASSERT_DELTA(outLam->y(0)[7], 0.886874, 1e-6);
   }
 
   void test_sum_in_q_exponential_correction() {
@@ -567,12 +570,12 @@ public:
     alg.setProperty("C1", 0.1);
     MatrixWorkspace_sptr outLam = runAlgorithmLam(alg, 11);
 
-    TS_ASSERT_DELTA(outLam->x(0)[0], 0.913144, 1e-6);
-    TS_ASSERT_DELTA(outLam->x(0)[3], 5.151752, 1e-6);
-    TS_ASSERT_DELTA(outLam->x(0)[7], 10.803229, 1e-6);
-    TS_ASSERT_DELTA(outLam->y(0)[0], 16.353662, 1e-6);
-    TS_ASSERT_DELTA(outLam->y(0)[3], 24.261270, 1e-6);
-    TS_ASSERT_DELTA(outLam->y(0)[7], 39.844321, 1e-6);
+    TS_ASSERT_DELTA(outLam->x(0)[0], 0.920496, 1e-6);
+    TS_ASSERT_DELTA(outLam->x(0)[3], 5.159104, 1e-6);
+    TS_ASSERT_DELTA(outLam->x(0)[7], 10.810581, 1e-6);
+    TS_ASSERT_DELTA(outLam->y(0)[0], 16.351599, 1e-6);
+    TS_ASSERT_DELTA(outLam->y(0)[3], 23.963539, 1e-6);
+    TS_ASSERT_DELTA(outLam->y(0)[7], 39.756738, 1e-6);
   }
 
   void test_sum_in_q_IvsQ() {
@@ -590,13 +593,38 @@ public:
     MatrixWorkspace_sptr outQ = runAlgorithmQ(alg, 11);
 
     // X range in outQ
-    TS_ASSERT_DELTA(outQ->x(0)[0], 0.292253, 1e-6);
-    TS_ASSERT_DELTA(outQ->x(0)[3], 0.393656, 1e-6);
-    TS_ASSERT_DELTA(outQ->x(0)[7], 0.732554, 1e-6);
+    TS_ASSERT_DELTA(outQ->x(0)[0], 0.292122, 1e-6);
+    TS_ASSERT_DELTA(outQ->x(0)[3], 0.393419, 1e-6);
+    TS_ASSERT_DELTA(outQ->x(0)[7], 0.731734, 1e-6);
     // Y counts
-    TS_ASSERT_DELTA(outQ->y(0)[0], 2.891639, 1e-6);
-    TS_ASSERT_DELTA(outQ->y(0)[3], 2.854571, 1e-6);
-    TS_ASSERT_DELTA(outQ->y(0)[7], 2.871364, 1e-6);
+    TS_ASSERT_DELTA(outQ->y(0)[0], 2.852088, 1e-6);
+    TS_ASSERT_DELTA(outQ->y(0)[3], 2.833380, 1e-6);
+    TS_ASSERT_DELTA(outQ->y(0)[7], 2.841288, 1e-6);
+  }
+
+  void test_sum_in_q_IvsQ_point_detector() {
+    // Test IvsQ workspace for a point detector
+    // No monitor normalization
+    // No direct beam normalization
+    // No transmission correction
+    // Processing instructions : 0
+
+    ReflectometryReductionOne2 alg;
+    setupAlgorithm(alg, 1.5, 15.0, "0");
+    alg.setProperty("InputWorkspace", m_singleDetectorWS);
+    alg.setProperty("SummationType", "SumInQ");
+    alg.setProperty("ReductionType", "DivergentBeam");
+    alg.setProperty("ThetaIn", 25.0);
+    MatrixWorkspace_sptr outQ = runAlgorithmQ(alg, 28);
+
+    // X range in outQ
+    TS_ASSERT_DELTA(outQ->x(0)[0], 0.279882, 1e-6);
+    TS_ASSERT_DELTA(outQ->x(0)[3], 0.310524, 1e-6);
+    TS_ASSERT_DELTA(outQ->x(0)[7], 0.363599, 1e-6);
+    // Y counts
+    TS_ASSERT_DELTA(outQ->y(0)[0], 2.900305, 1e-6);
+    TS_ASSERT_DELTA(outQ->y(0)[3], 2.886947, 1e-6);
+    TS_ASSERT_DELTA(outQ->y(0)[7], 2.607359, 1e-6);
   }
 
 private:
diff --git a/Framework/Crystal/inc/MantidCrystal/CentroidPeaks.h b/Framework/Crystal/inc/MantidCrystal/CentroidPeaks.h
index c621ce01755..f5e0c4fdfaa 100644
--- a/Framework/Crystal/inc/MantidCrystal/CentroidPeaks.h
+++ b/Framework/Crystal/inc/MantidCrystal/CentroidPeaks.h
@@ -39,6 +39,7 @@ private:
   void integrateEvent();
   int findPixelID(std::string bankName, int col, int row);
   void removeEdgePeaks(Mantid::DataObjects::PeaksWorkspace &peakWS);
+  void sizeBanks(const std::string &bankName, int &nCols, int &nRows);
   Geometry::Instrument_const_sptr inst;
 
   /// Input 2D Workspace
diff --git a/Framework/Crystal/src/CentroidPeaks.cpp b/Framework/Crystal/src/CentroidPeaks.cpp
index 4fa430714de..af82de22733 100644
--- a/Framework/Crystal/src/CentroidPeaks.cpp
+++ b/Framework/Crystal/src/CentroidPeaks.cpp
@@ -5,6 +5,8 @@
 #include "MantidKernel/VectorHelper.h"
 #include "MantidGeometry/Crystal/OrientedLattice.h"
 #include "MantidGeometry/Crystal/EdgePixel.h"
+#include "MantidGeometry/Instrument/ComponentInfo.h"
+#include <boost/algorithm/clamp.hpp>
 
 using Mantid::DataObjects::PeaksWorkspace;
 
@@ -106,6 +108,8 @@ void CentroidPeaks::integrate() {
     const auto &X = inWS->x(workspaceIndex);
     int chan = Kernel::VectorHelper::getBinIndex(X.rawData(), TOFPeakd);
     std::string bankName = peak.getBankName();
+    int nCols = 0, nRows = 0;
+    sizeBanks(bankName, nCols, nRows);
 
     double intensity = 0.0;
     double chancentroid = 0.0;
@@ -114,10 +118,10 @@ void CentroidPeaks::integrate() {
     int chanend = std::min(static_cast<int>(X.size()), chan + PeakRadius);
     double rowcentroid = 0.0;
     int rowstart = std::max(0, row - PeakRadius);
-    int rowend = row + PeakRadius;
+    int rowend = std::min(nRows - 1, row + PeakRadius);
     double colcentroid = 0.0;
-    int colstart = col - PeakRadius;
-    int colend = col + PeakRadius;
+    int colstart = std::max(0, col - PeakRadius);
+    int colend = std::min(nCols - 1, col + PeakRadius);
     for (int ichan = chanstart; ichan <= chanend; ++ichan) {
       for (int irow = rowstart; irow <= rowend; ++irow) {
         for (int icol = colstart; icol <= colend; ++icol) {
@@ -140,16 +144,15 @@ void CentroidPeaks::integrate() {
     }
     // Set pixelID to change row and col
     row = int(rowcentroid / intensity);
-    row = std::max(0, row);
+    boost::algorithm::clamp(row, 0, nRows - 1);
     col = int(colcentroid / intensity);
-    col = std::max(0, col);
+    boost::algorithm::clamp(col, 0, nCols - 1);
     chan = int(chancentroid / intensity);
-    chan = std::max(0, chan);
-    chan = std::min(static_cast<int>(inWS->blocksize()), chan);
+    boost::algorithm::clamp(chan, 0, static_cast<int>(inWS->blocksize()));
 
-    peak.setDetectorID(findPixelID(bankName, col, row));
     // Set wavelength to change tof for peak object
     if (!edgePixel(inst, bankName, col, row, Edge)) {
+      peak.setDetectorID(findPixelID(bankName, col, row));
       it = wi_to_detid_map.find(findPixelID(bankName, col, row));
       workspaceIndex = (it->second);
       Mantid::Kernel::Units::Wavelength wl;
@@ -224,6 +227,8 @@ void CentroidPeaks::integrateEvent() {
     int row = peak.getRow();
     double TOFPeakd = peak.getTOF();
     std::string bankName = peak.getBankName();
+    int nCols = 0, nRows = 0;
+    sizeBanks(bankName, nCols, nRows);
 
     double intensity = 0.0;
     double tofcentroid = 0.0;
@@ -234,10 +239,10 @@ void CentroidPeaks::integrateEvent() {
     double tofend = TOFPeakd * std::pow(1.004, PeakRadius);
     double rowcentroid = 0.0;
     int rowstart = std::max(0, row - PeakRadius);
-    int rowend = row + PeakRadius;
+    int rowend = std::min(nRows - 1, row + PeakRadius);
     double colcentroid = 0.0;
     int colstart = std::max(0, col - PeakRadius);
-    int colend = col + PeakRadius;
+    int colend = std::min(nCols - 1, col + PeakRadius);
     for (int irow = rowstart; irow <= rowend; ++irow) {
       for (int icol = colstart; icol <= colend; ++icol) {
         if (edgePixel(inst, bankName, icol, irow, Edge))
@@ -263,9 +268,9 @@ void CentroidPeaks::integrateEvent() {
     }
     // Set pixelID to change row and col
     row = int(rowcentroid / intensity);
-    row = std::max(0, row);
+    boost::algorithm::clamp(row, 0, nRows - 1);
     col = int(colcentroid / intensity);
-    col = std::max(0, col);
+    boost::algorithm::clamp(col, 0, nCols - 1);
     if (!edgePixel(inst, bankName, col, row, Edge)) {
       peak.setDetectorID(findPixelID(bankName, col, row));
 
@@ -340,7 +345,7 @@ void CentroidPeaks::removeEdgePeaks(
     Mantid::DataObjects::PeaksWorkspace &peakWS) {
   int Edge = getProperty("EdgePixels");
   std::vector<int> badPeaks;
-  size_t numPeaks = peakWS.getNumberPeaks() - 1;
+  size_t numPeaks = peakWS.getNumberPeaks();
   for (int i = 0; i < static_cast<int>(numPeaks); i++) {
     // Get a direct ref to that peak.
     const auto &peak = peakWS.getPeak(i);
@@ -355,5 +360,33 @@ void CentroidPeaks::removeEdgePeaks(
   peakWS.removePeaks(std::move(badPeaks));
 }
 
+void CentroidPeaks::sizeBanks(const std::string &bankName, int &nCols,
+                              int &nRows) {
+  if (bankName == "None")
+    return;
+  ExperimentInfo expInfo;
+  expInfo.setInstrument(inst);
+  const auto &compInfo = expInfo.componentInfo();
+
+  // Get a single bank
+  auto bank = inst->getComponentByName(bankName);
+  auto bankID = bank->getComponentID();
+  auto allBankDetectorIndexes =
+      compInfo.detectorsInSubtree(compInfo.indexOf(bankID));
+
+  nRows = static_cast<int>(
+      compInfo.componentsInSubtree(compInfo.indexOf(bankID)).size() -
+      allBankDetectorIndexes.size() - 1);
+  nCols = static_cast<int>(allBankDetectorIndexes.size()) / nRows;
+
+  if (nCols * nRows != static_cast<int>(allBankDetectorIndexes.size())) {
+    // Need grandchild instead of child
+    nRows = static_cast<int>(
+        compInfo.componentsInSubtree(compInfo.indexOf(bankID)).size() -
+        allBankDetectorIndexes.size() - 2);
+    nCols = static_cast<int>(allBankDetectorIndexes.size()) / nRows;
+  }
+}
+
 } // namespace Mantid
 } // namespace Crystal
diff --git a/Framework/Crystal/src/PeakHKLErrors.cpp b/Framework/Crystal/src/PeakHKLErrors.cpp
index cccd488248d..08e2cac4993 100644
--- a/Framework/Crystal/src/PeakHKLErrors.cpp
+++ b/Framework/Crystal/src/PeakHKLErrors.cpp
@@ -51,7 +51,7 @@ void PeakHKLErrors::init() {
   declareParameter("GonRotz", 0.0,
                    "1st Rotation of Goniometer about the z axis");
   initMode = 1;
-  if (OptRuns == "")
+  if (OptRuns.empty())
     return;
 
   initMode = 2;
diff --git a/Framework/Crystal/src/SCDPanelErrors.cpp b/Framework/Crystal/src/SCDPanelErrors.cpp
index 07fd84a64f6..d6da4f78916 100644
--- a/Framework/Crystal/src/SCDPanelErrors.cpp
+++ b/Framework/Crystal/src/SCDPanelErrors.cpp
@@ -263,7 +263,7 @@ void SCDPanelErrors::setAttribute(const std::string &attName,
     }
     FileValidator fval;
     std::string error = fval.isValid(fileName);
-    if (error == "") {
+    if (error.empty()) {
       storeAttributeValue(attName, Attribute(fileName, true));
       storeAttributeValue("Workspace", Attribute(""));
     } else {
diff --git a/Framework/Crystal/test/CentroidPeaksTest.h b/Framework/Crystal/test/CentroidPeaksTest.h
index 8da626a7a5d..58897139904 100644
--- a/Framework/Crystal/test/CentroidPeaksTest.h
+++ b/Framework/Crystal/test/CentroidPeaksTest.h
@@ -139,10 +139,14 @@ public:
     PeaksWorkspace_sptr pkws(new PeaksWorkspace());
     // pkws->setName("TOPAZ");
 
-    // Create a single peak on that particular detector
-    Peak PeakObj(in_ws->getInstrument(), 5050, 2., V3D(1, 1, 1));
+    // Create two peaks on that particular detector
+    // First peak is at edge to check EdgePixels option
+    Peak PeakObj(in_ws->getInstrument(), 0, 2., V3D(1, 1, 1));
     PeakObj.setRunNumber(3007);
     pkws->addPeak(PeakObj);
+    Peak PeakObj2(in_ws->getInstrument(), 5050, 2., V3D(1, 1, 1));
+    PeakObj2.setRunNumber(3007);
+    pkws->addPeak(PeakObj2);
     AnalysisDataService::Instance().addOrReplace("TOPAZ", pkws);
 
     inputW->mutableRun().addProperty("run_number", 3007);
diff --git a/Framework/CurveFitting/src/Algorithms/ConvertToYSpace.cpp b/Framework/CurveFitting/src/Algorithms/ConvertToYSpace.cpp
index 390821af3be..1db18580fae 100644
--- a/Framework/CurveFitting/src/Algorithms/ConvertToYSpace.cpp
+++ b/Framework/CurveFitting/src/Algorithms/ConvertToYSpace.cpp
@@ -289,7 +289,7 @@ void ConvertToYSpace::createOutputWorkspace() {
   m_outputWS->setYUnitLabel("");
 
   // q-Space output workspace
-  if (getPropertyValue("QWorkspace") != "") {
+  if (!getPropertyValue("QWorkspace").empty()) {
     m_qOutputWS = WorkspaceFactory::Instance().create(m_inputWS);
 
     m_qOutputWS->getAxis(0)->unit() = xLabel;
diff --git a/Framework/CurveFitting/src/Algorithms/Fit.cpp b/Framework/CurveFitting/src/Algorithms/Fit.cpp
index a0d584825b5..95f0f788000 100644
--- a/Framework/CurveFitting/src/Algorithms/Fit.cpp
+++ b/Framework/CurveFitting/src/Algorithms/Fit.cpp
@@ -138,7 +138,7 @@ void Fit::copyMinimizerOutput(const API::IFuncMinimizer &minimizer) {
   auto &properties = minimizer.getProperties();
   for (auto property : properties) {
     if ((*property).direction() == Kernel::Direction::Output &&
-        (*property).isValid() == "") {
+        (*property).isValid().empty()) {
       auto clonedProperty =
           std::unique_ptr<Kernel::Property>((*property).clone());
       declareProperty(std::move(clonedProperty));
diff --git a/Framework/CurveFitting/src/Algorithms/PlotPeakByLogValue.cpp b/Framework/CurveFitting/src/Algorithms/PlotPeakByLogValue.cpp
index 97d725beb89..b744e8480cf 100644
--- a/Framework/CurveFitting/src/Algorithms/PlotPeakByLogValue.cpp
+++ b/Framework/CurveFitting/src/Algorithms/PlotPeakByLogValue.cpp
@@ -646,7 +646,7 @@ std::string PlotPeakByLogValue::getMinimizerString(const std::string &wsName,
         dynamic_cast<Mantid::API::WorkspaceProperty<> *>(minimizerProp);
     if (wsProp) {
       const std::string &wsPropValue = minimizerProp->value();
-      if (wsPropValue != "") {
+      if (!wsPropValue.empty()) {
         const std::string &wsPropName = minimizerProp->name();
         m_minimizerWorkspaces[wsPropName].push_back(wsPropValue);
       }
diff --git a/Framework/CurveFitting/src/Algorithms/SplineInterpolation.cpp b/Framework/CurveFitting/src/Algorithms/SplineInterpolation.cpp
index 9cd6f62a192..1978323b913 100644
--- a/Framework/CurveFitting/src/Algorithms/SplineInterpolation.cpp
+++ b/Framework/CurveFitting/src/Algorithms/SplineInterpolation.cpp
@@ -223,7 +223,7 @@ void SplineInterpolation::exec() {
   }
   // Store the output workspaces
   std::string derivWsName = getPropertyValue("OutputWorkspaceDeriv");
-  if (order > 0 && derivWsName != "") {
+  if (order > 0 && !derivWsName.empty()) {
     // Store derivatives in a grouped workspace
     WorkspaceGroup_sptr wsg = WorkspaceGroup_sptr(new WorkspaceGroup);
     for (size_t i = 0; i < histNo; ++i) {
diff --git a/Framework/CurveFitting/src/Functions/CrystalFieldMultiSpectrum.cpp b/Framework/CurveFitting/src/Functions/CrystalFieldMultiSpectrum.cpp
index 009155d4c25..246fec09c7f 100644
--- a/Framework/CurveFitting/src/Functions/CrystalFieldMultiSpectrum.cpp
+++ b/Framework/CurveFitting/src/Functions/CrystalFieldMultiSpectrum.cpp
@@ -266,7 +266,7 @@ void CrystalFieldMultiSpectrum::calcExcitations(
   // using an index instead of a name for performance reasons
   auto &source = dynamic_cast<Peaks &>(*m_source);
   double intensityScaling;
-  if (source.m_IntensityScalingIdx.size() == 0) {
+  if (source.m_IntensityScalingIdx.empty()) {
     intensityScaling = getParameter(m_nOwnParams - nSpec + iSpec);
   } else {
     intensityScaling = getParameter(source.m_IntensityScalingIdx[iSpec]);
diff --git a/Framework/CurveFitting/src/Functions/TabulatedFunction.cpp b/Framework/CurveFitting/src/Functions/TabulatedFunction.cpp
index a10ed3ab8ab..a9fe5275204 100644
--- a/Framework/CurveFitting/src/Functions/TabulatedFunction.cpp
+++ b/Framework/CurveFitting/src/Functions/TabulatedFunction.cpp
@@ -174,7 +174,7 @@ void TabulatedFunction::setAttribute(const std::string &attName,
     }
     FileValidator fval;
     std::string error = fval.isValid(fileName);
-    if (error == "") {
+    if (error.empty()) {
       storeAttributeValue(attName, Attribute(fileName, true));
       storeAttributeValue("Workspace", Attribute(""));
     } else {
diff --git a/Framework/DataHandling/src/DataBlockComposite.cpp b/Framework/DataHandling/src/DataBlockComposite.cpp
index 73da8947215..50d25d844d1 100644
--- a/Framework/DataHandling/src/DataBlockComposite.cpp
+++ b/Framework/DataHandling/src/DataBlockComposite.cpp
@@ -412,6 +412,7 @@ void DataBlockComposite::removeSpectra(DataBlockComposite &toRemove) {
   // Get intervals for the data blocks which should be removed
   auto removeBlocks = toRemove.getDataBlocks();
   std::vector<std::pair<int64_t, int64_t>> toRemoveIntervals;
+  toRemoveIntervals.reserve(removeBlocks.size());
   for (const auto &dataBlock : removeBlocks) {
     toRemoveIntervals.emplace_back(dataBlock.getMinSpectrumID(),
                                    dataBlock.getMaxSpectrumID());
diff --git a/Framework/DataHandling/src/DownloadInstrument.cpp b/Framework/DataHandling/src/DownloadInstrument.cpp
index 5bd0af84487..1b45d90322b 100644
--- a/Framework/DataHandling/src/DownloadInstrument.cpp
+++ b/Framework/DataHandling/src/DownloadInstrument.cpp
@@ -204,7 +204,7 @@ DownloadInstrument::StringToStringMap DownloadInstrument::processRepository() {
     if ((sha != installSha) && (sha != localSha)) {
       fileMap.emplace(htmlUrl,
                       filePath.toString()); // ACTION - DOWNLOAD to localPath
-    } else if ((localSha != "") && (sha == installSha) &&
+    } else if ((!localSha.empty()) && (sha == installSha) &&
                (sha != localSha)) // matches install, but different local
     {
       fileMap.emplace(
diff --git a/Framework/DataHandling/src/LoadAscii2.cpp b/Framework/DataHandling/src/LoadAscii2.cpp
index e5ccb44c41b..3ffa5ed3b66 100644
--- a/Framework/DataHandling/src/LoadAscii2.cpp
+++ b/Framework/DataHandling/src/LoadAscii2.cpp
@@ -671,7 +671,7 @@ void LoadAscii2::exec() {
   std::string sep;
   // If the custom separator property is not empty, then we use that under any
   // circumstance.
-  if (custom != "") {
+  if (!custom.empty()) {
     sep = custom;
   }
   // Else if the separator drop down choice is not UserDefined then we use that.
diff --git a/Framework/DataHandling/src/LoadFITS.cpp b/Framework/DataHandling/src/LoadFITS.cpp
index 0754635782a..694bbb04743 100644
--- a/Framework/DataHandling/src/LoadFITS.cpp
+++ b/Framework/DataHandling/src/LoadFITS.cpp
@@ -293,7 +293,7 @@ void LoadFITS::loadHeader(const std::string &filePath, FITSInfo &header) {
   }
 
   // scale parameter, header BSCALE in the fits standard
-  if ("" == header.headerKeys[m_headerScaleKey]) {
+  if (header.headerKeys[m_headerScaleKey].empty()) {
     header.scale = 1;
   } else {
     try {
@@ -308,7 +308,7 @@ void LoadFITS::loadHeader(const std::string &filePath, FITSInfo &header) {
   }
 
   // data offsset parameter, header BZERO in the fits standard
-  if ("" == header.headerKeys[m_headerOffsetKey]) {
+  if (header.headerKeys[m_headerOffsetKey].empty()) {
     header.offset = 0;
   } else {
     try {
@@ -361,7 +361,7 @@ void LoadFITS::loadHeader(const std::string &filePath, FITSInfo &header) {
 void LoadFITS::headerSanityCheck(const FITSInfo &hdr,
                                  const FITSInfo &hdrFirst) {
   bool valid = true;
-  if (hdr.extension != "") {
+  if (!hdr.extension.empty()) {
     valid = false;
     g_log.error() << "File " << hdr.filePath
                   << ": extensions found in the header.\n";
@@ -625,7 +625,7 @@ void LoadFITS::parseHeader(FITSInfo &headerInfo) {
         if (key == g_END_KEYNAME)
           endFound = true;
 
-        if (key != "")
+        if (!key.empty())
           headerInfo.headerKeys[key] = value;
       }
     }
@@ -1141,7 +1141,7 @@ void LoadFITS::setupDefaultKeywordNames() {
  *  Maps the header keys to specified values
  */
 void LoadFITS::mapHeaderKeys() {
-  if ("" == getPropertyValue(g_HEADER_MAP_NAME))
+  if (getPropertyValue(g_HEADER_MAP_NAME).empty())
     return;
 
   // If a map file is selected, use that.
@@ -1157,19 +1157,19 @@ void LoadFITS::mapHeaderKeys() {
       while (getline(fStream, line)) {
         boost::split(lineSplit, line, boost::is_any_of("="));
 
-        if (lineSplit[0] == g_ROTATION_NAME && lineSplit[1] != "")
+        if (lineSplit[0] == g_ROTATION_NAME && !lineSplit[1].empty())
           m_headerRotationKey = lineSplit[1];
 
-        if (lineSplit[0] == g_BIT_DEPTH_NAME && lineSplit[1] != "")
+        if (lineSplit[0] == g_BIT_DEPTH_NAME && !lineSplit[1].empty())
           m_headerBitDepthKey = lineSplit[1];
 
-        if (lineSplit[0] == g_AXIS_NAMES_NAME && lineSplit[1] != "") {
+        if (lineSplit[0] == g_AXIS_NAMES_NAME && !lineSplit[1].empty()) {
           m_headerAxisNameKeys.clear();
           boost::split(m_headerAxisNameKeys, lineSplit[1],
                        boost::is_any_of(","));
         }
 
-        if (lineSplit[0] == g_IMAGE_KEY_NAME && lineSplit[1] != "") {
+        if (lineSplit[0] == g_IMAGE_KEY_NAME && !lineSplit[1].empty()) {
           m_headerImageKeyKey = lineSplit[1];
         }
       }
diff --git a/Framework/DataHandling/src/LoadFullprofResolution.cpp b/Framework/DataHandling/src/LoadFullprofResolution.cpp
index 32c8bc93506..cb0e15e0128 100644
--- a/Framework/DataHandling/src/LoadFullprofResolution.cpp
+++ b/Framework/DataHandling/src/LoadFullprofResolution.cpp
@@ -189,7 +189,7 @@ void LoadFullprofResolution::exec() {
   // Generate output table workspace
   API::ITableWorkspace_sptr outTabWs = genTableWorkspace(bankparammap);
 
-  if (getPropertyValue("OutputTableWorkspace") != "") {
+  if (!getPropertyValue("OutputTableWorkspace").empty()) {
     // Output the output table workspace
     setProperty("OutputTableWorkspace", outTabWs);
   }
@@ -225,7 +225,7 @@ void LoadFullprofResolution::exec() {
         loadParamAlg->execute();
       }
     }
-  } else if (getPropertyValue("OutputTableWorkspace") == "") {
+  } else if (getPropertyValue("OutputTableWorkspace").empty()) {
     // We don't know where to output
     throw std::runtime_error(
         "Either the OutputTableWorkspace or Workspace property must be set.");
diff --git a/Framework/DataHandling/src/LoadGSASInstrumentFile.cpp b/Framework/DataHandling/src/LoadGSASInstrumentFile.cpp
index 239ea187ff4..ca4e25413be 100644
--- a/Framework/DataHandling/src/LoadGSASInstrumentFile.cpp
+++ b/Framework/DataHandling/src/LoadGSASInstrumentFile.cpp
@@ -151,7 +151,7 @@ void LoadGSASInstrumentFile::exec() {
   WorkspaceGroup_sptr wsg = getProperty("Workspace");
   // Generate output table workspace
   API::ITableWorkspace_sptr outTabWs = genTableWorkspace(bankparammap);
-  if (getPropertyValue("OutputTableWorkspace") != "") {
+  if (!getPropertyValue("OutputTableWorkspace").empty()) {
     // Output the output table workspace
     setProperty("OutputTableWorkspace", outTabWs);
   }
diff --git a/Framework/DataHandling/src/LoadIDFFromNexus.cpp b/Framework/DataHandling/src/LoadIDFFromNexus.cpp
index f68ae54eb58..74305f3eedf 100644
--- a/Framework/DataHandling/src/LoadIDFFromNexus.cpp
+++ b/Framework/DataHandling/src/LoadIDFFromNexus.cpp
@@ -88,7 +88,7 @@ void LoadIDFFromNexus::exec() {
   // Look for parameter correction file
   std::string parameterCorrectionFile =
       getPropertyValue("ParameterCorrectionFilePath");
-  if (parameterCorrectionFile == "") {
+  if (parameterCorrectionFile.empty()) {
     parameterCorrectionFile =
         getParameterCorrectionFile(localWorkspace->getInstrument()->getName());
   }
@@ -98,7 +98,7 @@ void LoadIDFFromNexus::exec() {
   // Read parameter correction file, if found
   std::string correctionParameterFile;
   bool append = false;
-  if (parameterCorrectionFile != "") {
+  if (!parameterCorrectionFile.empty()) {
     // Read parameter correction file
     // to find out which parameter file to use
     // and whether it is appended to default parameters.
@@ -112,7 +112,7 @@ void LoadIDFFromNexus::exec() {
 
   // Load default parameters if either there is no correction parameter file or
   // it is to be appended.
-  if (correctionParameterFile == "" || append) {
+  if (correctionParameterFile.empty() || append) {
     LoadParameters(&nxfile, localWorkspace);
   } else { // Else clear the parameters
     g_log.notice() << "Parameters to be replaced are cleared.\n";
@@ -120,7 +120,7 @@ void LoadIDFFromNexus::exec() {
   }
 
   // Load parameters from correction parameter file, if it exists
-  if (correctionParameterFile != "") {
+  if (!correctionParameterFile.empty()) {
     Poco::Path corrFilePath(parameterCorrectionFile);
     g_log.debug() << "Correction file path: " << corrFilePath.toString()
                   << "\n";
@@ -200,7 +200,7 @@ void LoadIDFFromNexus::readParameterCorrectionFile(
   append = false;
 
   // Check the date.
-  if (date == "") {
+  if (date.empty()) {
     g_log.notice() << "No date is supplied for parameter correction file "
                    << correction_file << ". Correction file is ignored.\n";
     return;
diff --git a/Framework/DataHandling/src/LoadILLDiffraction.cpp b/Framework/DataHandling/src/LoadILLDiffraction.cpp
index 707c1e4661b..f04af6189a0 100644
--- a/Framework/DataHandling/src/LoadILLDiffraction.cpp
+++ b/Framework/DataHandling/src/LoadILLDiffraction.cpp
@@ -480,7 +480,7 @@ std::vector<double> LoadILLDiffraction::getScannedVaribleByPropertyName(
     }
   }
 
-  if (scannedVariable.size() == 0)
+  if (scannedVariable.empty())
     throw std::runtime_error(
         "Can not load file because scanned variable with property name " +
         propertyName + " was not found");
diff --git a/Framework/DataHandling/src/LoadILLIndirect2.cpp b/Framework/DataHandling/src/LoadILLIndirect2.cpp
index 4a40a4d3eb8..9360967a296 100644
--- a/Framework/DataHandling/src/LoadILLIndirect2.cpp
+++ b/Framework/DataHandling/src/LoadILLIndirect2.cpp
@@ -130,7 +130,7 @@ void LoadILLIndirect2::exec() {
 void LoadILLIndirect2::setInstrumentName(
     const NeXus::NXEntry &firstEntry, const std::string &instrumentNamePath) {
 
-  if (instrumentNamePath == "") {
+  if (instrumentNamePath.empty()) {
     std::string message("Cannot set the instrument name from the Nexus file!");
     g_log.error(message);
     throw std::runtime_error(message);
diff --git a/Framework/DataHandling/src/LoadILLReflectometry.cpp b/Framework/DataHandling/src/LoadILLReflectometry.cpp
index 8a5b69d8b49..f5f647ae829 100644
--- a/Framework/DataHandling/src/LoadILLReflectometry.cpp
+++ b/Framework/DataHandling/src/LoadILLReflectometry.cpp
@@ -176,7 +176,7 @@ void LoadILLReflectometry::exec() {
 void LoadILLReflectometry::setInstrumentName(
     const NeXus::NXEntry &firstEntry, const std::string &instrumentNamePath) {
 
-  if (instrumentNamePath == "") {
+  if (instrumentNamePath.empty()) {
     std::string message("Cannot set the instrument name from the Nexus file!");
     g_log.error(message);
     throw std::runtime_error(message);
diff --git a/Framework/DataHandling/src/LoadILLSANS.cpp b/Framework/DataHandling/src/LoadILLSANS.cpp
index 4e04e099d39..b9c0d3667cb 100644
--- a/Framework/DataHandling/src/LoadILLSANS.cpp
+++ b/Framework/DataHandling/src/LoadILLSANS.cpp
@@ -107,7 +107,7 @@ void LoadILLSANS::exec() {
 void LoadILLSANS::setInstrumentName(const NeXus::NXEntry &firstEntry,
                                     const std::string &instrumentNamePath) {
 
-  if (instrumentNamePath == "") {
+  if (instrumentNamePath.empty()) {
     std::string message("Cannot set the instrument name from the Nexus file!");
     g_log.error(message);
     throw std::runtime_error(message);
diff --git a/Framework/DataHandling/src/LoadILLTOF2.cpp b/Framework/DataHandling/src/LoadILLTOF2.cpp
index 50f76c1d024..9cc3f2e0a70 100644
--- a/Framework/DataHandling/src/LoadILLTOF2.cpp
+++ b/Framework/DataHandling/src/LoadILLTOF2.cpp
@@ -144,7 +144,7 @@ void LoadILLTOF2::loadInstrumentDetails(NeXus::NXEntry &firstEntry) {
 
   m_instrumentPath = m_loader.findInstrumentNexusPath(firstEntry);
 
-  if (m_instrumentPath == "") {
+  if (m_instrumentPath.empty()) {
     throw std::runtime_error(
         "Cannot set the instrument name from the Nexus file!");
   }
diff --git a/Framework/DataHandling/src/LoadIsawDetCal.cpp b/Framework/DataHandling/src/LoadIsawDetCal.cpp
index f2a479594d1..54638a6518b 100644
--- a/Framework/DataHandling/src/LoadIsawDetCal.cpp
+++ b/Framework/DataHandling/src/LoadIsawDetCal.cpp
@@ -92,7 +92,7 @@ std::map<std::string, std::string> LoadIsawDetCal::validateInputs() {
 
   // two detcal files is only valid for snap
   std::vector<std::string> filenames = getFilenames();
-  if (filenames.size() == 0) {
+  if (filenames.empty()) {
     result["Filename"] = "Must supply .detcal file";
   } else if (filenames.size() == 2) {
     Workspace_const_sptr wksp = getProperty("InputWorkspace");
diff --git a/Framework/DataHandling/src/LoadLLB.cpp b/Framework/DataHandling/src/LoadLLB.cpp
index 83f047c8782..6599483496a 100644
--- a/Framework/DataHandling/src/LoadLLB.cpp
+++ b/Framework/DataHandling/src/LoadLLB.cpp
@@ -104,7 +104,7 @@ void LoadLLB::setInstrumentName(NeXus::NXEntry &entry) {
   m_instrumentName =
       m_loader.getStringFromNexusPath(entry, m_instrumentPath + "/name");
 
-  if (m_instrumentName == "") {
+  if (m_instrumentName.empty()) {
     throw std::runtime_error(
         "Cannot read the instrument name from the Nexus file!");
   }
diff --git a/Framework/DataHandling/src/LoadMLZ.cpp b/Framework/DataHandling/src/LoadMLZ.cpp
index 1ad5df05084..23159b7e790 100644
--- a/Framework/DataHandling/src/LoadMLZ.cpp
+++ b/Framework/DataHandling/src/LoadMLZ.cpp
@@ -149,7 +149,7 @@ void LoadMLZ::loadInstrumentDetails(NeXus::NXEntry &firstEntry) {
 
   m_instrumentPath = m_mlzloader.findInstrumentNexusPath(firstEntry);
 
-  if (m_instrumentPath == "") {
+  if (m_instrumentPath.empty()) {
     throw std::runtime_error(
         "Cannot set the instrument name from the Nexus file!");
   }
diff --git a/Framework/DataHandling/src/LoadSINQFocus.cpp b/Framework/DataHandling/src/LoadSINQFocus.cpp
index d7a6a56b7ba..3494437cfa3 100644
--- a/Framework/DataHandling/src/LoadSINQFocus.cpp
+++ b/Framework/DataHandling/src/LoadSINQFocus.cpp
@@ -112,7 +112,7 @@ void LoadSINQFocus::setInstrumentName(NeXus::NXEntry &entry) {
 
   m_instrumentPath = m_loader.findInstrumentNexusPath(entry);
 
-  if (m_instrumentPath == "") {
+  if (m_instrumentPath.empty()) {
     throw std::runtime_error(
         "Cannot set the instrument name from the Nexus file!");
   }
diff --git a/Framework/DataHandling/src/LoadSassena.cpp b/Framework/DataHandling/src/LoadSassena.cpp
index 3713595548f..ba2054ea9fd 100644
--- a/Framework/DataHandling/src/LoadSassena.cpp
+++ b/Framework/DataHandling/src/LoadSassena.cpp
@@ -136,6 +136,7 @@ LoadSassena::loadQvectors(const hid_t &h5file, API::WorkspaceGroup_sptr gws,
 
   if (getProperty("SortByQVectors")) {
     std::vector<mypair> qvmodpair;
+    qvmodpair.reserve(nq);
     for (int iq = 0; iq < nq; iq++)
       qvmodpair.emplace_back(qvmod[iq], iq);
     std::sort(qvmodpair.begin(), qvmodpair.end(), compare);
diff --git a/Framework/DataHandling/src/LoadSpice2D.cpp b/Framework/DataHandling/src/LoadSpice2D.cpp
index c6a47228130..04096230e33 100644
--- a/Framework/DataHandling/src/LoadSpice2D.cpp
+++ b/Framework/DataHandling/src/LoadSpice2D.cpp
@@ -468,6 +468,7 @@ void LoadSpice2D::setBeamTrapRunProperty(
 
   // store trap diameters in use
   std::vector<double> trapDiametersInUse;
+  trapDiametersInUse.reserve(trapIndexInUse.size());
   for (auto index : trapIndexInUse) {
     trapDiametersInUse.push_back(trapDiameters[index]);
   }
diff --git a/Framework/DataHandling/src/LoadSpiceXML2DDet.cpp b/Framework/DataHandling/src/LoadSpiceXML2DDet.cpp
index eb822378272..f8eda027c8d 100644
--- a/Framework/DataHandling/src/LoadSpiceXML2DDet.cpp
+++ b/Framework/DataHandling/src/LoadSpiceXML2DDet.cpp
@@ -231,7 +231,7 @@ void LoadSpiceXML2DDet::processInputs() {
   if (vec_pixelgeom.size() == 2) {
     m_numPixelX = vec_pixelgeom[0];
     m_numPixelY = vec_pixelgeom[1];
-  } else if (vec_pixelgeom.size() == 0) {
+  } else if (vec_pixelgeom.empty()) {
     m_numPixelX = 0;
     m_numPixelY = 0;
   } else {
@@ -714,7 +714,7 @@ LoadSpiceXML2DDet::parseDetectorNode(const std::string &detvaluestr,
   size_t num_empty_line = 0;
   size_t num_weird_line = 0;
   for (size_t iline = 0; iline < vecLines.size(); ++iline) {
-    if (vecLines[iline].size() == 0)
+    if (vecLines[iline].empty())
       ++num_empty_line;
     else if (vecLines[iline].size() < 100)
       ++num_weird_line;
diff --git a/Framework/DataHandling/src/LoadTBL.cpp b/Framework/DataHandling/src/LoadTBL.cpp
index 1eeaff23a34..b5ab7f31e63 100644
--- a/Framework/DataHandling/src/LoadTBL.cpp
+++ b/Framework/DataHandling/src/LoadTBL.cpp
@@ -342,7 +342,7 @@ void LoadTBL::exec() {
     std::string line;
     int stitchID = 1;
     while (Kernel::Strings::extractToEOL(file, line)) {
-      if (line == "" || line == ",,,,,,,,,,,,,,,,") {
+      if (line.empty() || line == ",,,,,,,,,,,,,,,,") {
         continue;
       }
       getCells(line, rowVec, 16, isOld);
@@ -351,8 +351,8 @@ void LoadTBL::exec() {
 
       // check if the first run in the row has any data associated with it
       // 0 = runs, 1 = theta, 2 = trans, 3 = qmin, 4 = qmax
-      if (rowVec[0] != "" || rowVec[1] != "" || rowVec[2] != "" ||
-          rowVec[3] != "" || rowVec[4] != "") {
+      if (!rowVec[0].empty() || !rowVec[1].empty() || !rowVec[2].empty() ||
+          !rowVec[3].empty() || !rowVec[4].empty()) {
         TableRow row = ws->appendRow();
         row << stitchStr;
         for (int i = 0; i < 5; ++i) {
@@ -364,8 +364,8 @@ void LoadTBL::exec() {
 
       // check if the second run in the row has any data associated with it
       // 5 = runs, 6 = theta, 7 = trans, 8 = qmin, 9 = qmax
-      if (rowVec[5] != "" || rowVec[6] != "" || rowVec[7] != "" ||
-          rowVec[8] != "" || rowVec[9] != "") {
+      if (!rowVec[5].empty() || !rowVec[6].empty() || !rowVec[7].empty() ||
+          !rowVec[8].empty() || !rowVec[9].empty()) {
         TableRow row = ws->appendRow();
         row << stitchStr;
         for (int i = 5; i < 10; ++i) {
@@ -377,8 +377,8 @@ void LoadTBL::exec() {
 
       // check if the third run in the row has any data associated with it
       // 10 = runs, 11 = theta, 12 = trans, 13 = qmin, 14 = qmax
-      if (rowVec[10] != "" || rowVec[11] != "" || rowVec[12] != "" ||
-          rowVec[13] != "" || rowVec[14] != "") {
+      if (!rowVec[10].empty() || !rowVec[11].empty() || !rowVec[12].empty() ||
+          !rowVec[13].empty() || !rowVec[14].empty()) {
         TableRow row = ws->appendRow();
         row << stitchStr;
         for (int i = 10; i < 17; ++i) {
@@ -413,7 +413,7 @@ void LoadTBL::exec() {
     }
     size_t expectedCommas = columnHeadings.size() - 1;
     while (Kernel::Strings::extractToEOL(file, line)) {
-      if (line == "" || line == ",,,,,,,,,,,,,,,,") {
+      if (line.empty() || line == ",,,,,,,,,,,,,,,,") {
         // skip over any empty lines
         continue;
       }
diff --git a/Framework/DataHandling/src/RemoveLogs.cpp b/Framework/DataHandling/src/RemoveLogs.cpp
index e29203f4db2..78d2a8a5358 100644
--- a/Framework/DataHandling/src/RemoveLogs.cpp
+++ b/Framework/DataHandling/src/RemoveLogs.cpp
@@ -62,6 +62,7 @@ void RemoveLogs::exec() {
       localWorkspace->run().getLogData();
   std::vector<std::string> keepLogs = getProperty("KeepLogs");
   std::vector<std::string> logNames;
+  logNames.reserve(logData.size());
   for (const auto property : logData) {
     logNames.push_back(property->name());
   }
diff --git a/Framework/DataHandling/src/SaveAscii.cpp b/Framework/DataHandling/src/SaveAscii.cpp
index 090452f8089..2b10f7211f0 100644
--- a/Framework/DataHandling/src/SaveAscii.cpp
+++ b/Framework/DataHandling/src/SaveAscii.cpp
@@ -111,7 +111,7 @@ void SaveAscii::exec() {
   std::string sep;
   // If the custom separator property is not empty, then we use that under any
   // circumstance.
-  if (custom != "") {
+  if (!custom.empty()) {
     sep = custom;
   }
   // Else if the separator drop down choice is not UserDefined then we use that.
diff --git a/Framework/DataHandling/src/SaveAscii2.cpp b/Framework/DataHandling/src/SaveAscii2.cpp
index e44f640c9e5..55a10b34c34 100644
--- a/Framework/DataHandling/src/SaveAscii2.cpp
+++ b/Framework/DataHandling/src/SaveAscii2.cpp
@@ -166,7 +166,7 @@ void SaveAscii2::exec() {
   const std::string custom = getPropertyValue("CustomSeparator");
   // If the custom separator property is not empty, then we use that under any
   // circumstance.
-  if (custom != "") {
+  if (!custom.empty()) {
     m_sep = custom;
   }
   // Else if the separator drop down choice is not UserDefined then we use that.
diff --git a/Framework/DataHandling/src/SaveDiffFittingAscii.cpp b/Framework/DataHandling/src/SaveDiffFittingAscii.cpp
index 1a499698b74..c7fa62e19a9 100644
--- a/Framework/DataHandling/src/SaveDiffFittingAscii.cpp
+++ b/Framework/DataHandling/src/SaveDiffFittingAscii.cpp
@@ -86,6 +86,7 @@ bool SaveDiffFittingAscii::processGroups() {
         AnalysisDataService::Instance().retrieveWS<WorkspaceGroup>(name);
 
     std::vector<API::ITableWorkspace_sptr> input_ws;
+    input_ws.reserve(inputGroup->getNumberOfEntries());
     for (int i = 0; i < inputGroup->getNumberOfEntries(); ++i) {
       input_ws.push_back(
           boost::dynamic_pointer_cast<ITableWorkspace>(inputGroup->getItem(i)));
diff --git a/Framework/DataHandling/src/SaveReflCustomAscii.cpp b/Framework/DataHandling/src/SaveReflCustomAscii.cpp
index 6b9a61892c1..2630e931e3e 100644
--- a/Framework/DataHandling/src/SaveReflCustomAscii.cpp
+++ b/Framework/DataHandling/src/SaveReflCustomAscii.cpp
@@ -34,7 +34,7 @@ void SaveReflCustomAscii::extraHeaders(std::ofstream &file) {
   std::string subtitleEntry;
   std::string title = getProperty("Title");
 
-  if (title != "") // if is toggled
+  if (!title.empty()) // if is toggled
   {
     file << "#" << title << '\n';
   }
diff --git a/Framework/DataHandling/src/SaveReflThreeColumnAscii.cpp b/Framework/DataHandling/src/SaveReflThreeColumnAscii.cpp
index 29b9218c24e..cf8c9cd7013 100644
--- a/Framework/DataHandling/src/SaveReflThreeColumnAscii.cpp
+++ b/Framework/DataHandling/src/SaveReflThreeColumnAscii.cpp
@@ -28,7 +28,7 @@ void SaveReflThreeColumnAscii::extraHeaders(std::ofstream &file) {
   auto samp = m_ws->run();
   std::string title = getProperty("Title");
 
-  if (title != "") // if is toggled
+  if (!title.empty()) // if is toggled
   {
     file << "#" << title << '\n';
   }
diff --git a/Framework/DataHandling/src/SaveToSNSHistogramNexus.cpp b/Framework/DataHandling/src/SaveToSNSHistogramNexus.cpp
index b44854c1b2f..38625424c86 100644
--- a/Framework/DataHandling/src/SaveToSNSHistogramNexus.cpp
+++ b/Framework/DataHandling/src/SaveToSNSHistogramNexus.cpp
@@ -560,13 +560,13 @@ int SaveToSNSHistogramNexus::WriteGroup(int is_definition) {
           }
 
           //---------------------------------------------------------------------------------------
-          if (data_label == "data" && (bank != "")) {
+          if (data_label == "data" && (!bank.empty())) {
             if (this->WriteDataGroup(bank, is_definition) != NX_OK)
               return NX_ERROR;
             ;
           }
           //---------------------------------------------------------------------------------------
-          else if (data_label == "time_of_flight" && (bank != "")) {
+          else if (data_label == "time_of_flight" && (!bank.empty())) {
             // Get the original info
             if (NXgetinfo(inId, &dataRank, dataDimensions, &dataType) != NX_OK)
               return NX_ERROR;
diff --git a/Framework/DataObjects/src/EventList.cpp b/Framework/DataObjects/src/EventList.cpp
index 9471b3bf95b..64dd418bad5 100644
--- a/Framework/DataObjects/src/EventList.cpp
+++ b/Framework/DataObjects/src/EventList.cpp
@@ -4403,7 +4403,7 @@ void EventList::splitByPulseTimeWithMatrix(
   }
 
   // Split
-  if (vec_target.size() == 0) {
+  if (vec_target.empty()) {
     // No splitter: copy all events to group workspace = -1
     (*outputs[-1]) = (*this);
   } else {
diff --git a/Framework/DataObjects/src/MDHistoWorkspace.cpp b/Framework/DataObjects/src/MDHistoWorkspace.cpp
index 0b2c563a922..d08c9ffeef6 100644
--- a/Framework/DataObjects/src/MDHistoWorkspace.cpp
+++ b/Framework/DataObjects/src/MDHistoWorkspace.cpp
@@ -130,6 +130,7 @@ MDHistoWorkspace::~MDHistoWorkspace() {
 void MDHistoWorkspace::init(
     std::vector<Mantid::Geometry::MDHistoDimension_sptr> &dimensions) {
   std::vector<IMDDimension_sptr> dim2;
+  dim2.reserve(dimensions.size());
   for (auto &dimension : dimensions)
     dim2.push_back(boost::dynamic_pointer_cast<IMDDimension>(dimension));
   this->init(dim2);
diff --git a/Framework/DataObjects/src/PeaksWorkspace.cpp b/Framework/DataObjects/src/PeaksWorkspace.cpp
index a08a5e0b6e5..d38c71e1347 100644
--- a/Framework/DataObjects/src/PeaksWorkspace.cpp
+++ b/Framework/DataObjects/src/PeaksWorkspace.cpp
@@ -156,20 +156,19 @@ void PeaksWorkspace::removePeak(const int peakNum) {
 void PeaksWorkspace::removePeaks(std::vector<int> badPeaks) {
   if (badPeaks.empty())
     return;
-  std::sort(badPeaks.begin(), badPeaks.end());
-  auto index = peaks.begin();
-  auto end = peaks.end();
-  auto result = index;
-  for (int i = 0; index < end; ++index, ++i) {
-    // if index of peak is not in badPeaks
-    if (!std::binary_search(badPeaks.begin(), badPeaks.end(), i)) {
-      // include in result
-      *result = std::move(*index);
-      ++result;
-    }
-  }
-  // erase peaks outside of result
-  peaks.erase(result, peaks.end());
+  // if index of peak is in badPeaks remove
+  int ip = -1;
+  auto it =
+      std::remove_if(peaks.begin(), peaks.end(), [&ip, badPeaks](Peak &pk) {
+        (void)pk;
+        ip++;
+        for (auto ibp = badPeaks.begin(); ibp != badPeaks.end(); ++ibp) {
+          if (*ibp == ip)
+            return true;
+        }
+        return false;
+      });
+  peaks.erase(it, peaks.end());
 }
 
 //---------------------------------------------------------------------------------------------
diff --git a/Framework/Geometry/src/Crystal/ProductOfCyclicGroups.cpp b/Framework/Geometry/src/Crystal/ProductOfCyclicGroups.cpp
index 3aa2a64504e..460194a240c 100644
--- a/Framework/Geometry/src/Crystal/ProductOfCyclicGroups.cpp
+++ b/Framework/Geometry/src/Crystal/ProductOfCyclicGroups.cpp
@@ -31,7 +31,7 @@ ProductOfCyclicGroups::getGeneratedGroup(const std::string &generators) const {
 std::vector<Group_const_sptr> ProductOfCyclicGroups::getFactorGroups(
     const std::vector<SymmetryOperation> &symmetryOperations) const {
   std::vector<Group_const_sptr> groups;
-
+  groups.reserve(symmetryOperations.size());
   for (const auto &symmetryOperation : symmetryOperations) {
     groups.push_back(
         GroupFactory::create<CyclicGroup>(symmetryOperation.identifier()));
diff --git a/Framework/Geometry/src/Crystal/SymmetryElementFactory.cpp b/Framework/Geometry/src/Crystal/SymmetryElementFactory.cpp
index 09c5dfd1b37..471c225be68 100644
--- a/Framework/Geometry/src/Crystal/SymmetryElementFactory.cpp
+++ b/Framework/Geometry/src/Crystal/SymmetryElementFactory.cpp
@@ -313,7 +313,7 @@ std::string SymmetryElementMirrorGenerator::determineSymbol(
    * proper symbol, so the general symbol "g" is used for these cases.
    * Examples can be found in No. 227 (Fd-3m).
    */
-  if (symbol == "") {
+  if (symbol.empty()) {
     return "g";
   }
 
diff --git a/Framework/Geometry/src/Crystal/SymmetryOperationFactory.cpp b/Framework/Geometry/src/Crystal/SymmetryOperationFactory.cpp
index 35651a9b6ba..4630469d7cc 100644
--- a/Framework/Geometry/src/Crystal/SymmetryOperationFactory.cpp
+++ b/Framework/Geometry/src/Crystal/SymmetryOperationFactory.cpp
@@ -32,6 +32,7 @@ SymmetryOperationFactoryImpl::createSymOps(const std::string &identifiers) {
 std::vector<SymmetryOperation> SymmetryOperationFactoryImpl::createSymOps(
     const std::vector<std::string> &identifiers) {
   std::vector<SymmetryOperation> symOps;
+  symOps.reserve(identifiers.size());
   for (const auto &identifier : identifiers) {
     symOps.push_back(createSymOp(boost::trim_copy(identifier)));
   }
diff --git a/Framework/Geometry/src/Instrument/InstrumentDefinitionParser.cpp b/Framework/Geometry/src/Instrument/InstrumentDefinitionParser.cpp
index f079aa5fa59..62e0b6d875c 100644
--- a/Framework/Geometry/src/Instrument/InstrumentDefinitionParser.cpp
+++ b/Framework/Geometry/src/Instrument/InstrumentDefinitionParser.cpp
@@ -1282,7 +1282,7 @@ void InstrumentDefinitionParser::createDetectorOrMonitor(
     std::stringstream ss1, ss2;
     ss1 << idList.vec.size();
     ss2 << idList.counted;
-    if (idList.idname == "") {
+    if (idList.idname.empty()) {
       g_log.error("No list of detector IDs found for location element " + name);
       throw Kernel::Exception::InstrumentDefinitionError(
           "Detector location element " + name + " has no idlist.", filename);
diff --git a/Framework/Geometry/src/MDGeometry/MDGeometryXMLParser.cpp b/Framework/Geometry/src/MDGeometry/MDGeometryXMLParser.cpp
index 8cb0b237fa1..14f37e578f8 100644
--- a/Framework/Geometry/src/MDGeometry/MDGeometryXMLParser.cpp
+++ b/Framework/Geometry/src/MDGeometry/MDGeometryXMLParser.cpp
@@ -93,8 +93,10 @@ void MDGeometryXMLParser::execute() {
       throw std::invalid_argument("Cannot determine x-dimension mapping.");
     }
     m_xDimension = *xDimensionIt;
-    vecNonMappedDims.erase(std::remove_if(
-        vecNonMappedDims.begin(), vecNonMappedDims.end(), findID(xDimId)));
+    vecNonMappedDims.erase(std::remove_if(vecNonMappedDims.begin(),
+                                          vecNonMappedDims.end(),
+                                          findID(xDimId)),
+                           vecNonMappedDims.end());
   }
 
   Poco::XML::Element *yDimensionElement = geometryXMLElement->getChildElement(
@@ -112,8 +114,10 @@ void MDGeometryXMLParser::execute() {
       throw std::invalid_argument("Cannot determine y-dimension mapping.");
     }
     m_yDimension = *yDimensionIt;
-    vecNonMappedDims.erase(std::remove_if(
-        vecNonMappedDims.begin(), vecNonMappedDims.end(), findID(yDimId)));
+    vecNonMappedDims.erase(std::remove_if(vecNonMappedDims.begin(),
+                                          vecNonMappedDims.end(),
+                                          findID(yDimId)),
+                           vecNonMappedDims.end());
   }
 
   Poco::XML::Element *zDimensionElement = geometryXMLElement->getChildElement(
@@ -131,8 +135,10 @@ void MDGeometryXMLParser::execute() {
       throw std::invalid_argument("Cannot determine z-dimension mapping.");
     }
     m_zDimension = *zDimensionIt;
-    vecNonMappedDims.erase(std::remove_if(
-        vecNonMappedDims.begin(), vecNonMappedDims.end(), findID(zDimId)));
+    vecNonMappedDims.erase(std::remove_if(vecNonMappedDims.begin(),
+                                          vecNonMappedDims.end(),
+                                          findID(zDimId)),
+                           vecNonMappedDims.end());
   }
 
   Poco::XML::Element *tDimensionElement = geometryXMLElement->getChildElement(
@@ -150,8 +156,10 @@ void MDGeometryXMLParser::execute() {
     }
     m_tDimension = *tDimensionIt;
     if (!vecNonMappedDims.empty()) {
-      vecNonMappedDims.erase(std::remove_if(
-          vecNonMappedDims.begin(), vecNonMappedDims.end(), findID(tDimId)));
+      vecNonMappedDims.erase(std::remove_if(vecNonMappedDims.begin(),
+                                            vecNonMappedDims.end(),
+                                            findID(tDimId)),
+                             vecNonMappedDims.end());
     }
   }
   m_vecNonMappedDims = vecNonMappedDims; // Copy with strong guarantee.
diff --git a/Framework/HistogramData/src/CountStandardDeviations.cpp b/Framework/HistogramData/src/CountStandardDeviations.cpp
index 40fc5823bcd..439692a50a0 100644
--- a/Framework/HistogramData/src/CountStandardDeviations.cpp
+++ b/Framework/HistogramData/src/CountStandardDeviations.cpp
@@ -24,7 +24,7 @@ CountStandardDeviations::CountStandardDeviations(
     throw std::logic_error("CountStandardDeviations: Cannot construct from "
                            "FrequencyStandardDeviations -- BinEdges are NULL.");
   if ((frequencies.size() + 1) != edges.size())
-    if (frequencies.size() != 0 || edges.size() != 0)
+    if (!frequencies.empty() || !edges.empty())
       throw std::logic_error("CountStandardDeviations: Cannot construct from "
                              "FrequencyStandardDeviations -- BinEdges size "
                              "does not "
diff --git a/Framework/HistogramData/src/CountVariances.cpp b/Framework/HistogramData/src/CountVariances.cpp
index 3153e6f540e..baf7b9c4c55 100644
--- a/Framework/HistogramData/src/CountVariances.cpp
+++ b/Framework/HistogramData/src/CountVariances.cpp
@@ -23,7 +23,7 @@ CountVariances::CountVariances(FrequencyVariances &&frequencies,
     throw std::logic_error("CountVariances: Cannot construct from "
                            "FrequencyVariances -- BinEdges are NULL.");
   if ((frequencies.size() + 1) != edges.size())
-    if (frequencies.size() != 0 || edges.size() != 0)
+    if (!frequencies.empty() || !edges.empty())
       throw std::logic_error("CountVariances: Cannot construct from "
                              "FrequencyVariances -- BinEdges size does not "
                              "match.");
diff --git a/Framework/HistogramData/src/Counts.cpp b/Framework/HistogramData/src/Counts.cpp
index f63e47a0da0..3c5b7e0461f 100644
--- a/Framework/HistogramData/src/Counts.cpp
+++ b/Framework/HistogramData/src/Counts.cpp
@@ -17,7 +17,7 @@ Counts::Counts(Frequencies &&frequencies, const BinEdges &edges) {
     throw std::logic_error(
         "Counts: Cannot construct from Frequencies -- BinEdges are NULL.");
   if ((frequencies.size() + 1) != edges.size())
-    if (frequencies.size() != 0 || edges.size() != 0)
+    if (!frequencies.empty() || !edges.empty())
       throw std::logic_error("Counts: Cannot construct from Frequencies -- "
                              "BinEdges size does not match.");
   // Cannot move frequencies private data since it is of different type.
diff --git a/Framework/HistogramData/src/Frequencies.cpp b/Framework/HistogramData/src/Frequencies.cpp
index 21b8836351a..6623ee0b2f8 100644
--- a/Framework/HistogramData/src/Frequencies.cpp
+++ b/Framework/HistogramData/src/Frequencies.cpp
@@ -17,7 +17,7 @@ Frequencies::Frequencies(Counts &&counts, const BinEdges &edges) {
     throw std::logic_error(
         "Frequencies: Cannot construct from Counts -- BinEdges are NULL.");
   if ((counts.size() + 1) != edges.size())
-    if (counts.size() != 0 || edges.size() != 0)
+    if (!counts.empty() || !edges.empty())
       throw std::logic_error("Frequencies: Cannot construct from Counts -- "
                              "BinEdges size does not match.");
   // Cannot move counts private data since it is of different type.
diff --git a/Framework/HistogramData/src/FrequencyStandardDeviations.cpp b/Framework/HistogramData/src/FrequencyStandardDeviations.cpp
index a4b568727b1..99665e08cf8 100644
--- a/Framework/HistogramData/src/FrequencyStandardDeviations.cpp
+++ b/Framework/HistogramData/src/FrequencyStandardDeviations.cpp
@@ -23,7 +23,7 @@ FrequencyStandardDeviations::FrequencyStandardDeviations(
     throw std::logic_error("FrequencyStandardDeviations: Cannot construct from "
                            "CountStandardDeviations -- BinEdges are NULL.");
   if ((counts.size() + 1) != edges.size())
-    if (counts.size() != 0 || edges.size() != 0)
+    if (!counts.empty() || !edges.empty())
       throw std::logic_error("FrequencyStandardDeviations: Cannot construct "
                              "from CountStandardDeviations -- BinEdges size "
                              "does not match.");
diff --git a/Framework/HistogramData/src/FrequencyVariances.cpp b/Framework/HistogramData/src/FrequencyVariances.cpp
index 86c7c47bb61..a68c1c1ac07 100644
--- a/Framework/HistogramData/src/FrequencyVariances.cpp
+++ b/Framework/HistogramData/src/FrequencyVariances.cpp
@@ -23,7 +23,7 @@ FrequencyVariances::FrequencyVariances(CountVariances &&counts,
     throw std::logic_error("FrequencyVariances: Cannot construct from "
                            "CountVariances -- BinEdges are NULL.");
   if ((counts.size() + 1) != edges.size())
-    if (counts.size() != 0 || edges.size() != 0)
+    if (!counts.empty() || !edges.empty())
       throw std::logic_error("FrequencyVariances: Cannot construct from "
                              "CountVariances -- BinEdges size does not match.");
   // Cannot move counts private data since it is of different type.
diff --git a/Framework/HistogramData/src/Histogram.cpp b/Framework/HistogramData/src/Histogram.cpp
index d6367b8cd82..80b28a8e525 100644
--- a/Framework/HistogramData/src/Histogram.cpp
+++ b/Framework/HistogramData/src/Histogram.cpp
@@ -1,4 +1,5 @@
 #include "MantidHistogramData/Histogram.h"
+#include <sstream>
 
 namespace Mantid {
 namespace HistogramData {
@@ -251,8 +252,12 @@ template <> void Histogram::checkSize(const BinEdges &binEdges) const {
   // 0 points -> 0 edges, otherwise edges are 1 more than points.
   if (xMode() == XMode::Points && target > 0)
     target++;
-  if (target != binEdges.size())
-    throw std::logic_error("Histogram: size mismatch of BinEdges\n");
+  if (target != binEdges.size()) {
+    std::stringstream msg;
+    msg << "Histogram: size mismatch of BinEdges: (" << target
+        << " != " << binEdges.size() << ")";
+    throw std::logic_error(msg.str());
+  }
 }
 
 /** Resets the size of the internal x, dx, y, and e data structures
diff --git a/Framework/HistogramData/src/Points.cpp b/Framework/HistogramData/src/Points.cpp
index 78edbfac5fb..a375c2c64f9 100644
--- a/Framework/HistogramData/src/Points.cpp
+++ b/Framework/HistogramData/src/Points.cpp
@@ -10,7 +10,7 @@ Points::Points(const BinEdges &edges) {
     return;
   if (edges.size() == 1)
     throw std::logic_error("Points: Cannot construct from BinEdges of size 1");
-  if (edges.size() == 0) {
+  if (edges.empty()) {
     m_data = Kernel::make_cow<HistogramX>(0);
     return;
   }
diff --git a/Framework/Kernel/inc/MantidKernel/PropertyWithValue.tcc b/Framework/Kernel/inc/MantidKernel/PropertyWithValue.tcc
index 134f38a8d0c..a4902017dbb 100644
--- a/Framework/Kernel/inc/MantidKernel/PropertyWithValue.tcc
+++ b/Framework/Kernel/inc/MantidKernel/PropertyWithValue.tcc
@@ -271,7 +271,7 @@ TYPE &PropertyWithValue<TYPE>::operator=(const TYPE &value) {
     m_value = value;
   }
   std::string problem = this->isValid();
-  if (problem == "") {
+  if (problem.empty()) {
     return m_value;
   } else if (problem == "_alias") {
     m_value = getValueForAlias(value);
diff --git a/Framework/Kernel/src/CompositeValidator.cpp b/Framework/Kernel/src/CompositeValidator.cpp
index b87ecc4f290..55fd549ab0d 100644
--- a/Framework/Kernel/src/CompositeValidator.cpp
+++ b/Framework/Kernel/src/CompositeValidator.cpp
@@ -74,7 +74,7 @@ std::string CompositeValidator::check(const boost::any &value) const {
     std::string error = (*itr)->check(value);
     // exit on the first error, to avoid passing doing more tests on invalid
     // objects that could fail
-    if (error != "")
+    if (!error.empty())
       return error;
   }
   // there were no errors
diff --git a/Framework/Kernel/src/ConfigService.cpp b/Framework/Kernel/src/ConfigService.cpp
index 92f5b6b5d4c..59ca2639dfb 100644
--- a/Framework/Kernel/src/ConfigService.cpp
+++ b/Framework/Kernel/src/ConfigService.cpp
@@ -372,7 +372,7 @@ void ConfigServiceImpl::loadConfig(const std::string &filename,
     bool good = readFile(filename, temp);
 
     // check if we have failed to open the file
-    if ((!good) || (temp == "")) {
+    if ((!good) || (temp.empty())) {
       if (filename == getUserPropertiesDir() + m_user_properties_file_name) {
         // write out a fresh file
         createUserPropertiesFile();
@@ -382,7 +382,7 @@ void ConfigServiceImpl::loadConfig(const std::string &filename,
     }
 
     // store the property string
-    if ((append) && (m_PropertyString != "")) {
+    if ((append) && (!m_PropertyString.empty())) {
       m_PropertyString = m_PropertyString + "\n" + temp;
     } else {
       m_PropertyString = temp;
diff --git a/Framework/Kernel/src/UsageService.cpp b/Framework/Kernel/src/UsageService.cpp
index 5b77d1be8fa..54ab2d4a49e 100644
--- a/Framework/Kernel/src/UsageService.cpp
+++ b/Framework/Kernel/src/UsageService.cpp
@@ -263,7 +263,7 @@ std::string UsageServiceImpl::generateFeatureUsageMessage() {
       thisFeature["count"] = featureItem.second;
       features.append(thisFeature);
     }
-    if (features.size() > 0) {
+    if (!features.empty()) {
       message["features"] = features;
       return writer.write(message);
     }
diff --git a/Framework/MDAlgorithms/src/CompareMDWorkspaces.cpp b/Framework/MDAlgorithms/src/CompareMDWorkspaces.cpp
index e11d0a2a9c9..943900034c0 100644
--- a/Framework/MDAlgorithms/src/CompareMDWorkspaces.cpp
+++ b/Framework/MDAlgorithms/src/CompareMDWorkspaces.cpp
@@ -341,7 +341,7 @@ void CompareMDWorkspaces::exec() {
 
   this->doComparison();
 
-  if (m_result != "") {
+  if (!m_result.empty()) {
     g_log.notice() << "The workspaces did not match: " << m_result << '\n';
     this->setProperty("Equals", false);
   } else {
diff --git a/Framework/MDAlgorithms/src/ConvertToDiffractionMDWorkspace3.cpp b/Framework/MDAlgorithms/src/ConvertToDiffractionMDWorkspace3.cpp
index 3eb926cae75..639462ab745 100644
--- a/Framework/MDAlgorithms/src/ConvertToDiffractionMDWorkspace3.cpp
+++ b/Framework/MDAlgorithms/src/ConvertToDiffractionMDWorkspace3.cpp
@@ -70,7 +70,7 @@ void ConvertToDiffractionMDWorkspace3::convertExtents(
       minVal[d] = Extents[2 * d + 0];
       maxVal[d] = Extents[2 * d + 1];
     }
-  } else if (Extents.size() == 0) {
+  } else if (Extents.empty()) {
     calculateExtentsFromData(minVal, maxVal);
   } else
     throw std::invalid_argument(
diff --git a/Framework/MDAlgorithms/src/Integrate3DEvents.cpp b/Framework/MDAlgorithms/src/Integrate3DEvents.cpp
index 60178eb2fed..c59cb394f43 100644
--- a/Framework/MDAlgorithms/src/Integrate3DEvents.cpp
+++ b/Framework/MDAlgorithms/src/Integrate3DEvents.cpp
@@ -98,9 +98,9 @@ Integrate3DEvents::integrateStrongPeak(const IntegrationParameters &params,
   std::vector<V3D> eigen_vectors;
   getEigenVectors(cov_matrix, eigen_vectors);
 
-  std::vector<double> sigmas;
+  std::vector<double> sigmas(3);
   for (int i = 0; i < 3; i++) {
-    sigmas.push_back(stdDev(events, eigen_vectors[i], params.regionRadius));
+    sigmas[i] = stdDev(events, eigen_vectors[i], params.regionRadius);
   }
 
   bool invalid_peak =
@@ -248,9 +248,9 @@ double Integrate3DEvents::estimateSignalToNoiseRatio(
   std::vector<V3D> eigen_vectors;
   getEigenVectors(cov_matrix, eigen_vectors);
 
-  std::vector<double> sigmas;
+  std::vector<double> sigmas(3);
   for (int i = 0; i < 3; i++) {
-    sigmas.push_back(stdDev(events, eigen_vectors[i], params.regionRadius));
+    sigmas[i] = stdDev(events, eigen_vectors[i], params.regionRadius);
   }
 
   const auto max_sigma = *std::max_element(sigmas.begin(), sigmas.end());
@@ -404,9 +404,9 @@ Integrate3DEvents::ellipseIntegrateEvents(
   std::vector<V3D> eigen_vectors;
   getEigenVectors(cov_matrix, eigen_vectors);
 
-  std::vector<double> sigmas;
+  std::vector<double> sigmas(3);
   for (int i = 0; i < 3; i++) {
-    sigmas.push_back(stdDev(some_events, eigen_vectors[i], m_radius));
+    sigmas[i] = stdDev(some_events, eigen_vectors[i], m_radius);
   }
 
   bool invalid_peak =
diff --git a/Framework/Nexus/src/NexusFileIO.cpp b/Framework/Nexus/src/NexusFileIO.cpp
index 502ab8330ec..e373e252dbb 100644
--- a/Framework/Nexus/src/NexusFileIO.cpp
+++ b/Framework/Nexus/src/NexusFileIO.cpp
@@ -264,7 +264,7 @@ bool NexusFileIO::writeNxNote(const std::string &noteName,
   m_filehandle->makeGroup(noteName, "NXnote", true);
 
   std::vector<std::string> attributes, avalues;
-  if (date != "") {
+  if (!date.empty()) {
     attributes.emplace_back("date");
     avalues.push_back(date);
   }
diff --git a/Framework/PythonInterface/mantid/CMakeLists.txt b/Framework/PythonInterface/mantid/CMakeLists.txt
index 0787726737b..ea78516c4a8 100644
--- a/Framework/PythonInterface/mantid/CMakeLists.txt
+++ b/Framework/PythonInterface/mantid/CMakeLists.txt
@@ -15,6 +15,7 @@ set ( OUTPUT_DIR ${PYTHON_PKG_ROOT} )
 set ( PY_FILES
   __init__.py
   simpleapi.py
+  fitfunctions.py
 )
 
 copy_files_to_dir ( "${PY_FILES}" ${CMAKE_CURRENT_SOURCE_DIR} ${OUTPUT_DIR} 
diff --git a/Framework/PythonInterface/mantid/api/CMakeLists.txt b/Framework/PythonInterface/mantid/api/CMakeLists.txt
index 17a8ea2024c..c1a7146b29d 100644
--- a/Framework/PythonInterface/mantid/api/CMakeLists.txt
+++ b/Framework/PythonInterface/mantid/api/CMakeLists.txt
@@ -75,6 +75,7 @@ set ( EXPORT_FILES
   src/Exports/Projection.cpp
   src/Exports/FunctionProperty.cpp
   src/Exports/AlgorithmProperty.cpp
+  src/Exports/MultiDomainFunction.cpp
 )
 
 set ( MODULE_DEFINITION ${CMAKE_CURRENT_BINARY_DIR}/api.cpp )
diff --git a/Framework/PythonInterface/mantid/api/src/Exports/CompositeFunction.cpp b/Framework/PythonInterface/mantid/api/src/Exports/CompositeFunction.cpp
index 538cec6a046..9085e895a26 100644
--- a/Framework/PythonInterface/mantid/api/src/Exports/CompositeFunction.cpp
+++ b/Framework/PythonInterface/mantid/api/src/Exports/CompositeFunction.cpp
@@ -1,12 +1,39 @@
+#include "MantidPythonInterface/kernel/GetPointer.h"
 #include "MantidAPI/CompositeFunction.h"
 #include <boost/python/class.hpp>
+#include <boost/python/overloads.hpp>
+#include <boost/python/register_ptr_to_python.hpp>
 
 using Mantid::API::CompositeFunction;
 using Mantid::API::IFunction;
 using namespace boost::python;
 
+GET_POINTER_SPECIALIZATION(CompositeFunction)
+
+namespace {
+
+typedef double (CompositeFunction::*getParameterType1)(size_t) const;
+typedef double (CompositeFunction::*getParameterType2)(
+    const std::string &) const;
+
+typedef void (CompositeFunction::*setParameterType2)(const std::string &,
+                                                     const double &, bool);
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunknown-pragmas"
+#pragma clang diagnostic ignored "-Wunused-local-typedef"
+#endif
+BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(setParameterType2_Overloads,
+                                       setParameter, 2, 3)
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+} // namespace
+
 void export_CompositeFunction() {
 
+  register_ptr_to_python<boost::shared_ptr<CompositeFunction>>();
+
   class_<CompositeFunction, bases<IFunction>, boost::noncopyable>(
       "CompositeFunction", "Composite Fit functions")
       .def("nFunctions", &CompositeFunction::nFunctions, arg("self"),
@@ -17,6 +44,23 @@ void export_CompositeFunction() {
            (arg("self"), arg("i")), "Get the i-th function.")
       .def("__getitem__", &CompositeFunction::getFunction,
            (arg("self"), arg("i")), "Get the i-th function.")
+      .def("__setitem__", &CompositeFunction::replaceFunction,
+           (arg("self"), arg("i"), arg("f")),
+           "Put function in place of the i-th function.")
       .def("add", &CompositeFunction::addFunction,
-           (arg("self"), arg("function")), "Add a member function.");
+           (arg("self"), arg("function")), "Add a member function.")
+      .def("getParameterValue",
+           (getParameterType1)&CompositeFunction::getParameter,
+           (arg("self"), arg("i")), "Get value of parameter of given index.")
+      .def("getParameterValue",
+           (getParameterType2)&CompositeFunction::getParameter,
+           (arg("self"), arg("name")), "Get value of parameter of given name.")
+      .def("__getitem__", (getParameterType2)&CompositeFunction::getParameter,
+           (arg("self"), arg("name")), "Get value of parameter of given name.")
+      .def("__setitem__", (setParameterType2)&CompositeFunction::setParameter,
+           setParameterType2_Overloads(
+               (arg("self"), arg("name"), arg("value"), arg("explicitlySet")),
+               "Get value of parameter of given name."))
+      .def("__delitem__", &CompositeFunction::removeFunction,
+           (arg("self"), arg("index")));
 }
diff --git a/Framework/PythonInterface/mantid/api/src/Exports/FunctionFactory.cpp b/Framework/PythonInterface/mantid/api/src/Exports/FunctionFactory.cpp
index 1e72c23d461..bc9e49abf89 100644
--- a/Framework/PythonInterface/mantid/api/src/Exports/FunctionFactory.cpp
+++ b/Framework/PythonInterface/mantid/api/src/Exports/FunctionFactory.cpp
@@ -1,5 +1,6 @@
 #include "MantidAPI/FunctionFactory.h"
 #include "MantidAPI/IFunction.h"
+#include "MantidAPI/CompositeFunction.h"
 #include "MantidKernel/WarningSuppressions.h"
 #include "MantidPythonInterface/kernel/GetPointer.h"
 #include "MantidPythonInterface/kernel/PythonObjectInstantiator.h"
@@ -47,6 +48,28 @@ PyObject *getFunctionNames(FunctionFactoryImpl &self) {
   return registered;
 }
 
+//------------------------------------------------------------------------------------------------------
+/**
+* Something that makes Function Factory return to python a composite function
+* for Product function, Convolution or
+* any similar superclass of composite function.
+* @param self :: Enables it to be called as a member function on the
+* FunctionFactory class
+* @param name :: Name of the superclass of composite function,
+* e.g. "ProductFunction".
+*/
+Mantid::API::CompositeFunction_sptr
+createCompositeFunction(FunctionFactoryImpl &self, const std::string &name) {
+  auto fun = self.createFunction(name);
+  auto composite =
+      boost::dynamic_pointer_cast<Mantid::API::CompositeFunction>(fun);
+  if (composite) {
+    return composite;
+  }
+  std::string error_message = name + " is not a composite function.";
+  throw std::invalid_argument(error_message);
+}
+
 //--------------------------------------------- Function registration
 //------------------------------------------------
 
@@ -96,6 +119,9 @@ void export_FunctionFactory() {
                                                   no_init)
       .def("getFunctionNames", &getFunctionNames, arg("self"),
            "Returns a list of the currently available functions")
+      .def("createCompositeFunction", &createCompositeFunction,
+           (arg("self"), arg("name")),
+           "Return a pointer to the requested function")
       .def("createFunction", &FunctionFactoryImpl::createFunction,
            (arg("self"), arg("type")),
            "Return a pointer to the requested function")
diff --git a/Framework/PythonInterface/mantid/api/src/Exports/IAlgorithm.cpp b/Framework/PythonInterface/mantid/api/src/Exports/IAlgorithm.cpp
index 889e326b374..6115a7a2561 100644
--- a/Framework/PythonInterface/mantid/api/src/Exports/IAlgorithm.cpp
+++ b/Framework/PythonInterface/mantid/api/src/Exports/IAlgorithm.cpp
@@ -93,7 +93,7 @@ struct MandatoryFirst {
   /// in the list
   bool operator()(const Property *p1, const Property *p2) const {
     // this is false, unless p1 is not valid and p2 is valid
-    return (p1->isValid() != "") && (p2->isValid() == "");
+    return (!p1->isValid().empty()) && (p2->isValid().empty());
   }
 };
 
diff --git a/Framework/PythonInterface/mantid/api/src/Exports/IFunction.cpp b/Framework/PythonInterface/mantid/api/src/Exports/IFunction.cpp
index 686b34f78e7..26560ceb1b3 100644
--- a/Framework/PythonInterface/mantid/api/src/Exports/IFunction.cpp
+++ b/Framework/PythonInterface/mantid/api/src/Exports/IFunction.cpp
@@ -1,6 +1,7 @@
 #include "MantidKernel/WarningSuppressions.h"
 #include "MantidPythonInterface/kernel/GetPointer.h"
 #include "MantidPythonInterface/api/FitFunctions/IFunctionAdapter.h"
+#include "MantidAPI/CompositeFunction.h"
 
 #include <boost/python/class.hpp>
 #include <boost/python/def.hpp>
@@ -60,6 +61,8 @@ BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(fixParameter_Overloads, fixParameter, 1,
                                        2)
 BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(fix_Overloads, fix, 1, 2)
 BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(fixAll_Overloads, fixAll, 0, 1)
+typedef void (IFunction::*removeTieByName)(const std::string &);
+
 GCC_DIAG_ON(conversion)
 #ifdef __clang__
 #pragma clang diagnostic pop
@@ -92,6 +95,12 @@ void export_IFunction() {
       .def("attributeNames", &IFunction::getAttributeNames, arg("self"),
            "The names of all the attributes")
 
+      .def("hasAttribute", &IFunction::hasAttribute, (arg("self"), arg("name")),
+           "Return whether there is an attribute of the given name")
+
+      .def("hasParameter", &IFunction::hasParameter, (arg("self"), arg("name")),
+           "Return whether there is an parameter of the given name")
+
       .def("nParams", &IFunction::nParams, arg("self"),
            "Return the number of parameters")
 
@@ -114,6 +123,10 @@ void export_IFunction() {
                IFunction::getParameter,
            (arg("self"), arg("name")), "Get the value of the named parameter")
 
+      .def("__getitem__", (double (IFunction::*)(const std::string &) const) &
+                              IFunction::getParameter,
+           (arg("self"), arg("name")), "Get the value of the named parameter")
+
       .def("setParameter", (setParameterType1)&IFunction::setParameter,
            setParameterType1_Overloads(
                (arg("self"), arg("i"), arg("value"), arg("explicitlySet")),
@@ -124,6 +137,11 @@ void export_IFunction() {
                (arg("self"), arg("name"), arg("value"), arg("explicitlySet")),
                "Sets the value of the named parameter"))
 
+      .def("__setitem__", (setParameterType2)&IFunction::setParameter,
+           setParameterType2_Overloads(
+               (arg("self"), arg("name"), arg("value"), arg("explicitlySet")),
+               "Sets the value of the named parameter"))
+
       .def("declareAttribute", &IFunctionAdapter::declareAttribute,
            (arg("self"), arg("name"), arg("default_value")),
            "Declare an attribute with an initial value")
@@ -204,6 +222,9 @@ void export_IFunction() {
            "Split this function (if needed) into a list of "
            "independent functions")
 
+      .def("nDomains", &IFunction::getNumberDomains, arg("self"),
+           "Get the number of domains.")
+
       //-- Deprecated functions that have the wrong names --
       .def("categories", &getCategories, arg("self"),
            "Returns a list of the categories for an algorithm")
@@ -219,6 +240,7 @@ void export_IFunction() {
       .def("getParamValue",
            (double (IFunction::*)(std::size_t) const) & IFunction::getParameter,
            (arg("self"), arg("i")), "Get the value of the ith parameter")
+
       //-- Python special methods --
       .def("__repr__", &IFunction::asString, arg("self"),
            "Return a string representation of the function");
diff --git a/Framework/PythonInterface/mantid/api/src/Exports/MultiDomainFunction.cpp b/Framework/PythonInterface/mantid/api/src/Exports/MultiDomainFunction.cpp
new file mode 100644
index 00000000000..2751fb1c80e
--- /dev/null
+++ b/Framework/PythonInterface/mantid/api/src/Exports/MultiDomainFunction.cpp
@@ -0,0 +1,24 @@
+#include "MantidAPI/MultiDomainFunction.h"
+#include <boost/python/class.hpp>
+
+using Mantid::API::MultiDomainFunction;
+using Mantid::API::CompositeFunction;
+using namespace boost::python;
+
+void export_MultiDomainFunction() {
+  class_<MultiDomainFunction, bases<CompositeFunction>, boost::noncopyable>(
+      "MultiDomainFunction", "Multi-Domain Fit functions")
+      .def("nFunctions", &MultiDomainFunction::nFunctions, arg("self"),
+           "Get the number of member functions.")
+      .def("__len__", &MultiDomainFunction::nFunctions, arg("self"),
+           "Get the number of member functions.")
+      .def("getFunction", &MultiDomainFunction::getFunction,
+           (arg("self"), arg("i")), "Get the i-th function.")
+      .def("__getitem__", &MultiDomainFunction::getFunction,
+           (arg("self"), arg("i")), "Get the i-th function.")
+      .def("add", &MultiDomainFunction::addFunction,
+           (arg("self"), arg("function")), "Add a member function.")
+      .def("setDomainIndex", &MultiDomainFunction::setDomainIndex,
+           (arg("self"), arg("funIndex"), arg("domainIndex")),
+           "Associate a function and a domain.");
+}
diff --git a/Framework/PythonInterface/mantid/api/src/Exports/ProductFunction.cpp b/Framework/PythonInterface/mantid/api/src/Exports/ProductFunction.cpp
new file mode 100644
index 00000000000..d2ef62e9d76
--- /dev/null
+++ b/Framework/PythonInterface/mantid/api/src/Exports/ProductFunction.cpp
@@ -0,0 +1,48 @@
+#include "MantidAPI/ProductFunction.h"
+#include <boost/python/class.hpp>
+#include <boost/python/overloads.hpp>
+
+using Mantid::API::ProductFunction;
+using Mantid::API::IFunction;
+using namespace boost::python;
+
+namespace {
+
+typedef double (ProductFunction::*getParameterType1)(size_t) const;
+typedef double (ProductFunction::*getParameterType2)(const std::string &) const;
+
+typedef void (ProductFunction::*setParameterType2)(const std::string &,
+                                                   const double &, bool);
+BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(setParameterType2_Overloads,
+                                       setParameter, 2, 3)
+}
+
+void export_ProductFunction() {
+
+  class_<ProductFunction, bases<IFunction>, boost::noncopyable>(
+      "ProductFunction", "Composite Fit functions")
+      .def("nFunctions", &ProductFunction::nFunctions, arg("self"),
+           "Get the number of member functions.")
+      .def("__len__", &ProductFunction::nFunctions, arg("self"),
+           "Get the number of member functions.")
+      .def("getFunction", &ProductFunction::getFunction,
+           (arg("self"), arg("i")), "Get the i-th function.")
+      .def("__getitem__", &ProductFunction::getFunction,
+           (arg("self"), arg("i")), "Get the i-th function.")
+      .def("add", &ProductFunction::addFunction, (arg("self"), arg("function")),
+           "Add a member function.")
+      .def("getParameterValue",
+           (getParameterType1)&ProductFunction::getParameter,
+           (arg("self"), arg("i")), "Get value of parameter of given index.")
+      .def("getParameterValue",
+           (getParameterType2)&ProductFunction::getParameter,
+           (arg("self"), arg("name")), "Get value of parameter of given name.")
+      .def("__getitem__", (getParameterType2)&ProductFunction::getParameter,
+           (arg("self"), arg("name")), "Get value of parameter of given name.")
+      .def("__setitem__", (setParameterType2)&ProductFunction::setParameter,
+           setParameterType2_Overloads(
+               (arg("self"), arg("name"), arg("value"), arg("explicitlySet")),
+               "Get value of parameter of given name."))
+      .def("__delitem__", &ProductFunction::removeFunction,
+           (arg("self"), arg("index")));
+}
diff --git a/Framework/PythonInterface/mantid/fitfunctions.py b/Framework/PythonInterface/mantid/fitfunctions.py
new file mode 100644
index 00000000000..38ccdb912b1
--- /dev/null
+++ b/Framework/PythonInterface/mantid/fitfunctions.py
@@ -0,0 +1,523 @@
+from mantid.api import FunctionFactory
+ 
+class FunctionWrapper(object):
+  """ Wrapper class for Fitting Function 
+  """
+  def __init__ (self, name, **kwargs):
+    """ 
+    Called when creating an instance
+    :param name: name of fitting function to create or 
+    an Ifunction object to wrap.
+    :param **kwargs: standard argument for __init__ function
+    """
+    if not isinstance(name, str):
+       self.fun = name
+    else:
+       self.fun = FunctionFactory.createFunction(name)
+       # Deal with attributes first
+       for key in kwargs:
+          if key == "attributes":
+             atts = kwargs[key]
+             for keya in atts:
+               self.fun.setAttributeValue(keya, atts[keya])
+          elif self.fun.hasAttribute(key):
+             self.fun.setAttributeValue(key, kwargs[key])
+           
+       # Then deal with parameters        
+       for key in kwargs:
+          if key != "attributes" and not self.fun.hasAttribute(key):
+             self.fun.setParameter(key, kwargs[key])
+ 
+  def __getattr__(self, item):
+      """
+      __getattr__ invoked when attribute item not found in the instance,
+      nor in the class, nor its superclasses.
+      :param item: named attribute
+      :return: attribute of self.fun instance, or any of the fitting
+       parameters or function attributes
+      """
+      if 'fun' in self.__dict__:
+          if hasattr(self.fun, item):
+              return getattr(self.fun, item)
+          else:
+              return self.__getitem__(item)
+ 
+  def __setattr__(self, key, value):
+      """
+      __setattr__ invoked when attribute key not found in the instance,
+      nor in the class, nor its superclasses.
+      Enables direct access to the attributes of self.fun instance, and
+      also to its fitting parameters and function attributes
+      :param key: named attribute 
+      :param value: new value for the named attribute
+      """
+      if key == 'fun':
+          self.__dict__['fun'] = value  # initialize self.fun
+      elif 'fun' in self.__dict__ and hasattr(self.fun, key):
+          setattr(self.fun, key, value)  # key is attribute of instance self.fun
+      elif 'fun' in self.__dict__ and self.fun.hasAttribute(key):
+          self.fun.setAttributeValue(key, value)  # key is function attribute
+      elif 'fun' in self.__dict__ and self.fun.hasParameter(key):
+          self.fun.setParameter(key, value)  # key is fitting parameter
+      else:
+          self.__dict__[key] = value  # initialize self.key
+ 
+  def __getitem__ (self, name):
+      """ Called from array-like access on RHS
+          It should not be called directly.
+          :param name: name that appears in the []
+      """
+      if type(name) == type('string') and self.fun.hasAttribute(name):
+         return self.fun.getAttributeValue(name)
+      else:
+         return self.fun.getParameterValue(name)
+       
+  def __setitem__ (self, name, value):
+      """ Called from array-like access on LHS
+          It should not be called directly.
+       
+          :param name: name that appears in the []
+          :param value: new value of this item
+      """
+      if type(name) == type('string') and self.fun.hasAttribute(name):
+         self.fun.setAttributeValue(name, value)
+      else:
+         self.fun.setParameter(name, value)
+       
+  def __str__ (self):
+      """ Return string giving contents of function.
+          Used in unit tests.
+      """
+      return self.fun.__str__()
+ 
+  def __add__ (self, other):
+      """ Implement + operator for composite function
+       
+          :param other: functionWrapper to be added to self
+      """
+      sum = CompositeFunctionWrapper(self, other)
+      if sum.pureAddition:
+         sum = sum.flatten()
+      return sum    
+         
+  def __mul__ (self, other):
+      """ Implement * operator for product function
+       
+          :param other: functionWrapper to multiply self by
+      """
+      prod = ProductFunctionWrapper(self, other)
+      if prod.pureMultiplication:
+         prod = prod.flatten()
+      return prod
+         
+  def tie (self, *args, **kwargs):
+    """ Add ties.
+        :param *args: one or more dictionaries of ties
+        :param **kwargs: one or more ties
+    """
+    for a in args:
+        if isinstance(a, dict):
+          for key in a:
+            self.fun.tie(key, str(a[key]))
+ 
+    for key in kwargs:
+        self.fun.tie(key, str(kwargs[key]))
+         
+  def fix(self, name):
+      """ Fix a parameter.
+       
+          :param name: name of parameter to be fixed
+      """
+      self.fun.fixParameter(name)
+       
+  def fixAllParameters(self):
+       """ Fix all parameters.
+       """
+       for i in range(0, self.fun.numParams()):
+          self.fix(self.getParameterName(i))
+       
+  def untie(self, name):
+      """ Remove tie from parameter.
+       
+          :param name: name of parameter to be untied
+      """
+      self.fun.removeTie(name)
+       
+  def untieAllParameters(self):
+      """ Remove ties from all parameters.
+      """
+      for i in range(0, self.fun.numParams()):  
+          self.fun.removeTie(self.getParameterName(i))  
+ 
+           
+  def constrain(self, expressions):
+      """ Add constraints
+       
+          :param expressions: string of tie expressions      
+      """
+      self.fun.addConstraints( expressions )
+       
+  def unconstrain(self, name):
+      """ Remove constraints from a parameter
+       
+          :param name: name of parameter to be unconstrained
+      """
+      self.fun.removeConstraint(name)
+            
+  def free(self, name):
+      """ Free a parameter from tie or constraint
+       
+          :param name: name of parameter to be freed
+      """
+      self.fun.removeTie(name)
+      self.fun.removeConstraint(name)
+       
+  def getParameterName(self, index):
+      """ Get the name of the parameter of given index
+       
+          :param index: index of parameter
+      """
+      return self.fun.getParamName(index)
+        
+  @property
+  def function(self):
+      """ Return the underlying IFunction object
+      """      
+      return self.fun
+ 
+  @property
+  def name(self):
+      """ Return the name of the function
+      """
+      return self.fun.name()
+       
+class CompositeFunctionWrapper(FunctionWrapper):
+    """ Wrapper class for Composite Fitting Function
+    """
+    def __init__ (self, *args):
+       """ Called when creating an instance
+           It should not be called directly
+           :param *args: names of functions in composite function
+       """
+       self.pureAddition = True
+       self.pureMultiplication = False
+       return self.initByName("CompositeFunction", *args)
+ 
+    def initByName(self, name, *args):
+       """ intialise composite function of named type.
+           E.g. "ProductFunction"
+           This function would be protected in c++
+           and should not be called directly except
+           by __init__ functions of this class and
+           subclasses.
+            
+          :param name: name of class calling this.
+          :param *args: names of functions in composite function
+       """
+       if len(args) == 1 and  not isinstance(args[0], FunctionWrapper):
+          # We have a composite function to wrap
+          self.fun = args[0]
+       else:
+          self.fun = FunctionFactory.createCompositeFunction(name)
+    
+          #Add the functions, checking for Composite & Product functions
+          for a in args:
+             if not isinstance(a, int):
+                if isinstance(a, CompositeFunctionWrapper):
+                   if self.pureAddition:
+                      self.pureAddition = a.pureAddition
+                   if self.pureMultiplication:
+                      self.pureMultiplication = a.pureMultiplication
+                functionToAdd = FunctionFactory.createInitialized( a.fun.__str__() )             
+                self.fun.add(functionToAdd)    
+       
+    def getParameter(self, name):
+        """ get value of parameter of specified name
+       
+            :param name: name of parameter
+        """
+        return self.fun.getParameterValue(name)
+         
+    def getCompositeParameterName(self, name, index):
+        """ get composite parameter name of parameter of 
+            given name of member function of given index
+        """
+        return "f"+str(index)+"."+name
+         
+    def getIndexOfFunction (self, name):
+        """ get index of function specified by name,
+            such as "LinearBackground" for the only
+            LinearBackground function or
+            "Gaussian1" for the second Gaussian function.
+       
+            :param name: name specifying the function
+        """
+        # Only a shallow search is done.
+         
+        delimiter = " "
+        if name.count(delimiter) == 0:
+           fname = name
+           occurrence = 0
+        else:
+           fname, n = name.split(delimiter)
+           occurrence = int(n)
+            
+        index = 0
+        count = 0
+        for f in self:
+           if f.fun.name() == fname:
+              if( count == occurrence):
+                 return index
+              else:
+                 count += 1
+           index += 1
+          
+        raise RuntimeError("Specified function not found.")
+         
+    def f (self, name):
+        """ get function specified by name,
+            such as "LinearBackground" for the only
+            LinearBackground function or
+            "Gaussian1" for the second Gaussian function.
+       
+            :param name: name specifying the function
+        """
+        index = self.getIndexOfFunction(name)
+        return self[index]
+ 
+    def __getitem__ (self, nameorindex):
+        """ get function of specified index or parameter of specified name
+            called for array-like access on RHS.
+            It should not be called directly.
+       
+            :param name: name or index in the []
+        """
+ 
+        comp = self.fun
+        item = comp[nameorindex]
+        if isinstance(item, float):
+           return  item
+        elif item.name() == "CompositeFunction":
+           return CompositeFunctionWrapper(item)
+        elif item.name() == "ProductFunction":
+           return ProductFunctionWrapper(item)
+        elif item.name() == "Convolution":
+           return ConvolutionWrapper(item)
+        else:       
+           return FunctionWrapper(item)
+ 
+    def __setitem__ (self, name, newValue):
+        """ Called from array-like access on LHS
+            It should not be called directly.
+       
+            :param name: name or index in the []
+            :param newValue: new value for item
+        """
+        comp = self.fun
+        if isinstance( newValue, FunctionWrapper):
+           comp[name] = newValue.fun
+        else:
+           comp[name] = newValue
+                     
+    def __iadd__ (self, other):
+       """ Implement += operator.
+           It should not be called directly.
+            
+           :param other: object to add
+       """
+       self.fun.add(other.fun)
+       return self
+        
+    def __delitem__ (self, index):
+       """ Delete item of given index from composite function.
+           It should not be called directly.
+            
+           :param index: index of item
+       """
+       self.fun.__delitem__(index)
+        
+    def __len__ (self):
+       """ Return number of items in composite function.
+           Implement len() function.
+           It should not be called directly.
+       """
+ 
+       composite = self.fun
+       return composite.__len__()
+
+        
+    def tieAll (self, name):
+       """ For each member function, tie the parameter of the given name 
+           to the parameter of that name in the first member function.
+           The named parameter must occur in all the member functions.
+            
+           :param name: name of parameter
+       """
+       expr = self.getCompositeParameterName(name, 0)
+       self.tie({self.getCompositeParameterName(name, i): expr for i in range(1,len(self)) }) 
+           
+    def fixAll (self, name):
+       """ Fix all parameters with the given local name.
+           Every member function must have a parameter of this name.
+            
+           :param name: name of parameter
+       """
+       for f in self:
+         f.fix(name)
+ 
+           
+    def constrainAll (self, expressions):
+       """ Constrain all parameters according local names in expressions.
+                   
+           :param expressions: string of expressions
+       """
+       for i in range(0, len(self)):
+          if isinstance( self[i], CompositeFunctionWrapper ):
+             self[i].constrainAll(expressions)
+          else:
+             try:
+                self[i].constrain(expressions)
+             except:
+                pass
+           
+    def unconstrainAll (self, name):
+       """ Unconstrain all parameters of given local name.
+            
+           :param name: local name of parameter
+       """
+       for i in range(0, len(self)):
+          if isinstance( self[i], CompositeFunctionWrapper ):
+             self[i].unconstrainAll(name)
+          else:
+             try:
+                self[i].unconstrain(name)
+             except:
+                pass
+           
+    def untieAll (self, name):
+       """ Untie all parameters with the given local name.
+           Every member function must have a parameter of this name.
+            
+           :param name: local name of parameter
+       """
+       for i in range(0, len(self)):
+          self.untie(self.getCompositeParameterName(name, i))
+ 
+    def flatten (self):
+       """ Return composite function, equal to self, but with 
+           every composite function within replaced by
+           its list of functions, so having a pure list of functions.
+           This makes it possible to index and iterate all the functions
+           and use tieAll() and untieAll().
+           Not to be used with a mixture of product and sum 
+           composite functions, because the arithmetic 
+           may no longer be correct.
+           The return value is not independent of self.
+       """
+       # If there are no composite functions, do nothing
+       needToFlatten = False
+       for i in range(0, len(self)):
+          if not needToFlatten and isinstance(self[i],CompositeFunctionWrapper):
+             needToFlatten = True
+              
+       if not needToFlatten :
+          return self
+        
+       # Now we know there is a composite function.
+       if isinstance(self,ProductFunctionWrapper):
+          flatSelf = ProductFunctionWrapper()
+       else:
+          flatSelf = CompositeFunctionWrapper()
+           
+       for i in range(0, len(self)):
+          if isinstance(self[i],CompositeFunctionWrapper):
+             currentFunction = self[i].flatten()
+             for j in range(0, len(currentFunction)):
+                flatSelf.fun.add(currentFunction[j].fun)
+          else:
+             flatSelf.fun.add(self[i].fun)
+              
+       return flatSelf          
+  
+class ProductFunctionWrapper(CompositeFunctionWrapper):
+    """ Wrapper class for Product Fitting Function
+    """
+    def __init__ (self, *args):
+       """ Called when creating an instance
+           It should not be called directly.
+           :param *args: names of functions in composite function
+       """
+       self.pureAddition = False
+       self.pureMultiplication = True
+       return self.initByName("ProductFunction", *args)
+        
+class ConvolutionWrapper(CompositeFunctionWrapper):
+    """ Wrapper class for Convolution Fitting Function
+    """
+    def __init__ (self, *args):
+       """ Called when creating an instance
+           It should not be called directly.
+           :param *args: names of functions in composite function
+       """
+       self.pureAddition = False
+       self.pureMultiplication = False
+       return self.initByName("Convolution", *args)
+        
+class MultiDomainFunctionWrapper(CompositeFunctionWrapper):
+    """ Wrapper class for Product Fitting Function
+    """
+    def __init__ (self, *args, **kwargs):
+       """ Called when creating an instance
+           It should not be called directly
+           :param *args: names of functions in composite function
+       """
+       # Assume it's not safe to flatten
+       self.pureAddition = False
+       self.pureMultiplication = False
+        
+       # Create and populate with copied functions
+       self.initByName("MultiDomainFunction", *args)
+                
+       # Tie the global parameters
+       if 'Global' in kwargs:
+          list = kwargs['Global']
+          for name in list:
+             self.tieAll(name)
+        
+       # Set domain indices: 1 to 1       
+       for i in range(0, len(self)):
+          self.fun.setDomainIndex(i, i)
+     
+    @property     
+    def nDomains (self):
+       """ Return number of domains 
+       """
+       return self.fun.nDomains()
+      
+ 
+def _create_wrapper_function(name):
+    """Create fake functions for the given name
+       It should not be called directly
+                   
+       :param name: name of fake function
+    """
+    # ------------------------------------------------------------------------------------------------
+    def wrapper_function(*args, **kwargs):
+        name_to_constructor = {
+            'CompositeFunction': CompositeFunctionWrapper,
+            'ProductFunction': ProductFunctionWrapper,
+            'Convolution': ConvolutionWrapper,
+            'MultiDomainFunction': MultiDomainFunctionWrapper,
+            }
+        # constructor is FunctionWrapper if the name is not in the registry.
+        constructor = name_to_constructor.get(name, FunctionWrapper)  
+        return  constructor(name, *args, **kwargs)
+ 
+    # ------------------------------------------------------------------------------------------------
+    wrapper_function.__name__ = name
+    # _replace_signature(fake_function, ("", ""))
+    globals()[name] = wrapper_function
+ 
+fnames = FunctionFactory.getFunctionNames()
+for i, val in enumerate(fnames):
+   _create_wrapper_function(val)
diff --git a/Framework/PythonInterface/mantid/simpleapi.py b/Framework/PythonInterface/mantid/simpleapi.py
index 0cb3001e546..b0ad72f4e3b 100644
--- a/Framework/PythonInterface/mantid/simpleapi.py
+++ b/Framework/PythonInterface/mantid/simpleapi.py
@@ -36,6 +36,7 @@ from .kernel.funcinspect import customise_func as _customise_func
 from . import apiVersion, __gui__
 from .kernel._aliases import *
 from .api._aliases import *
+from .fitfunctions import *
 
 # ------------------------ Specialized function calls --------------------------
 # List of specialized algorithms
@@ -314,6 +315,10 @@ def fitting_algorithm(f):
         if type(function) == str and function in _api.AnalysisDataService:
             raise ValueError("Fit API has changed. The function must now come "
                              "first in the argument list and the workspace second.")
+        # Deal with case where function is a FunctionWrapper.
+        if isinstance(function,FunctionWrapper):
+            function = function.__str__()
+            
         # Create and execute
         algm = _create_algorithm_object(function_name)
         _set_logging_option(algm, kwargs)
@@ -361,7 +366,8 @@ def Fit(*args, **kwargs):
     It can work with arbitrary data sources and therefore some options
     are only available when the function & workspace type are known.
 
-    This simple wrapper takes the Function (as a string) & the InputWorkspace
+    This simple wrapper takes the Function (as a string or a
+    FunctionWrapper object) and the InputWorkspace
     as the first two arguments. The remaining arguments must be
     specified by keyword.
 
@@ -376,7 +382,7 @@ def Fit(*args, **kwargs):
 @fitting_algorithm
 def CalculateChiSquared(*args, **kwargs):
     """
-    This function calculates chi squared claculation for a function and a data set.
+    This function calculates chi squared calculation for a function and a data set.
     The data set is defined in a way similar to Fit algorithm.
 
     Example:
diff --git a/Framework/PythonInterface/test/python/mantid/CMakeLists.txt b/Framework/PythonInterface/test/python/mantid/CMakeLists.txt
index 438c607131f..ebc2daab04d 100644
--- a/Framework/PythonInterface/test/python/mantid/CMakeLists.txt
+++ b/Framework/PythonInterface/test/python/mantid/CMakeLists.txt
@@ -13,6 +13,7 @@ set ( TEST_PY_FILES
   SimpleAPILoadTest.py
   SimpleAPIFitTest.py
   SimpleAPIRenameWorkspaceTest.py
+  FitFunctionsTest.py  
 )
 
 if ( MAKE_VATES )
diff --git a/Framework/PythonInterface/test/python/mantid/FitFunctionsTest.py b/Framework/PythonInterface/test/python/mantid/FitFunctionsTest.py
new file mode 100644
index 00000000000..6ad62bef89e
--- /dev/null
+++ b/Framework/PythonInterface/test/python/mantid/FitFunctionsTest.py
@@ -0,0 +1,581 @@
+"""
+    Test of fitfunctions.py and related classes
+"""
+from __future__ import (absolute_import, division, print_function)
+
+import unittest
+import testhelpers
+import platform
+from mantid.simpleapi import CreateWorkspace, EvaluateFunction, Fit, FitDialog 
+from mantid.simpleapi import FunctionWrapper, CompositeFunctionWrapper, ProductFunctionWrapper, ConvolutionWrapper, MultiDomainFunctionWrapper 
+from mantid.simpleapi import Gaussian, LinearBackground, Polynomial
+from mantid.api import mtd, MatrixWorkspace, ITableWorkspace
+import numpy as np
+from testhelpers import run_algorithm
+
+class FitFunctionsTest(unittest.TestCase):
+
+    _raw_ws = None
+
+    def setUp(self):
+        pass
+        
+    def test_creation(self):
+        testhelpers.assertRaisesNothing(self, FunctionWrapper, "Gaussian", Height=7.5, Sigma=1.2, PeakCentre=10)
+    
+    def test_name(self):
+        g = FunctionWrapper( "Gaussian", Height=7.5, Sigma=1.2, PeakCentre=10)
+        self.assertEqual(g.name,"Gaussian")
+        
+    def test_read_array_elements(self):
+        g = FunctionWrapper( "Gaussian", Height=7.5, Sigma=1.2, PeakCentre=10)  
+        self.assertAlmostEqual(g["Height"],7.5,10)
+        
+    def test_write_array_elements(self):
+        g = FunctionWrapper( "Gaussian", Height=7.5, Sigma=1.2, PeakCentre=10) 
+        g["Height"] = 8
+        self.assertAlmostEqual(g["Height"],8,10)
+        
+    def test_dot_operator(self):
+        g = FunctionWrapper( "Gaussian", Height=7.5, Sigma=1.2, PeakCentre=10) 
+        self.assertAlmostEqual(g.Height,7.5,10)
+        g.Height = 8
+        self.assertAlmostEqual(g.Height,8,10)       
+        
+    def test_compositefunction_creation(self):
+        g0 = FunctionWrapper( "Gaussian", Height=7.5, Sigma=1.2, PeakCentre=10)
+        g1 = FunctionWrapper( "Gaussian", Height=8.5, Sigma=1.2, PeakCentre=11)
+        testhelpers.assertRaisesNothing(self, CompositeFunctionWrapper, g0, g1)  
+        
+    def test_copy_on_compositefunction_creation(self):
+        g0 = FunctionWrapper( "Gaussian", Height=7.5, Sigma=1.2, PeakCentre=10)
+        g1 = FunctionWrapper( "Gaussian", Height=8.5, Sigma=1.2, PeakCentre=11)
+        c = CompositeFunctionWrapper( g0, g1 )
+        g0["Height"] = 10.0
+        g1["Height"] = 11.0
+        # Check that the composite function remains unmodified.
+        self.assertAlmostEqual( c["f0.Height"],7.5)
+        self.assertAlmostEqual( c["f1.Height"],8.5)
+        
+    def test_getindexoffunction_in_compositefunction(self):
+        lb = FunctionWrapper("LinearBackground", A0=0.5, A1=1.5)
+        g0 = FunctionWrapper( "Gaussian", Height=7.5, Sigma=1.2, PeakCentre=10)
+        g1 = FunctionWrapper( "Gaussian", Height=8.5, Sigma=1.2, PeakCentre=11)
+        c = CompositeFunctionWrapper( lb, g0, g1 )
+        index_lb = c.getIndexOfFunction("LinearBackground")
+        self.assertEqual( index_lb, 0)
+        index_g0 = c.getIndexOfFunction("Gaussian 0")
+        self.assertEqual( index_g0, 1)
+        index_g1 = c.getIndexOfFunction("Gaussian 1")
+        self.assertEqual( index_g1, 2)
+        
+    def test_compositefunction_f (self):
+        lb = FunctionWrapper("LinearBackground", A0=0.5, A1=1.5)
+        g0 = FunctionWrapper( "Gaussian", Height=7.5, Sigma=1.2, PeakCentre=10)
+        g1 = FunctionWrapper( "Gaussian", Height=8.5, Sigma=1.2, PeakCentre=11)
+        c = CompositeFunctionWrapper( lb, g0, g1 )
+        
+        lbx = c.f("LinearBackground")
+        lbx_str = str(lbx)
+        lb_str = str(lb)
+        self.assertEqual(lbx_str, lb_str)
+        
+        g1x = c.f("Gaussian 1")
+        g1x_str = str(g1x)
+        g1_str = str(g1)
+        self.assertEqual(g1x_str, g1_str)
+
+    def test_compositefunction_read_array_elements(self):
+        lb = FunctionWrapper("LinearBackground", A0=0.5, A1=1.5)
+        g0 = FunctionWrapper( "Gaussian", Height=7.5, Sigma=1.2, PeakCentre=10)
+        g1 = FunctionWrapper( "Gaussian", Height=8.5, Sigma=1.2, PeakCentre=11)
+        c = CompositeFunctionWrapper( lb, g0, g1 )
+        
+        self.assertAlmostEqual(c["f0.A1"], 1.5,10)      
+        self.assertAlmostEqual(c["f1.Height"], 7.5,10)
+        self.assertAlmostEqual(c["f2.Height"], 8.5,10)
+
+        self.assertAlmostEqual(c[0]["A1"], 1.5,10)        
+        self.assertAlmostEqual(c[1]["Height"], 7.5,10)
+        self.assertAlmostEqual(c[2]["Height"], 8.5,10)
+        
+    def test_compositefunction_write_array_elements(self):
+        lb = FunctionWrapper("LinearBackground", A0=0.5, A1=1.5)
+        g0 = FunctionWrapper( "Gaussian", Height=7.5, Sigma=1.2, PeakCentre=10)
+        g1 = FunctionWrapper( "Gaussian", Height=8.5, Sigma=1.2, PeakCentre=11)
+        c = CompositeFunctionWrapper( lb, g0, g1 )
+             
+        c["f0.A1"] = 0.0
+        self.assertAlmostEqual(c["f0.A1"], 0.0,10) 
+        c[0]["A1"] = 1.0
+        self.assertAlmostEqual(c["f0.A1"], 1.0,10)
+        
+        c["f1.Height"] = 10.0
+        self.assertAlmostEqual(c[1]["Height"], 10.0,10) 
+        c[1]["Height"] = 11.0
+        self.assertAlmostEqual(c[1]["Height"], 11.0,10)
+        
+        g0a = FunctionWrapper( "Gaussian", Height=7.0, Sigma=1.2, PeakCentre=9)
+        c[1] = g0a
+        self.assertAlmostEqual(c[1]["Height"], 7.0,10)
+        
+    def test_attributes(self):
+        testhelpers.assertRaisesNothing(self, FunctionWrapper, "Polynomial", attributes={'n': 3}, A0=4, A1=3, A2=2, A3=1)
+        testhelpers.assertRaisesNothing(self, FunctionWrapper, "Polynomial", n=3, A0=4, A1=3, A2=2, A3=1)
+        p = Polynomial(n=3, A0=1, A1=2, A2=4, A3=3)
+        self.assertEqual(p['n'],3)
+        p['n'] = 4
+        self.assertEqual(p['n'],4)
+        
+    def test_fix(self):
+        g = FunctionWrapper( "Gaussian", Height=8.5, Sigma=1.2, PeakCentre=15)
+        
+        g.fix("Sigma")
+        g_str = str(g)
+        self.assertEqual(g_str.count("ties="),1)
+        self.assertEqual(g_str.count("ties=(Sigma=1.2)"),1)
+        
+        g0 = FunctionWrapper( "Gaussian", Height=7.5, Sigma=1.2, PeakCentre=10)
+        g1 = FunctionWrapper( "Gaussian", Height=8.5, Sigma=1.2, PeakCentre=11)
+        c = CompositeFunctionWrapper(g0, g1)
+        
+        c.fix("f1.Sigma")
+        c_str = str(c)
+        self.assertEqual(c_str.count("ties="),1)
+        self.assertEqual(c_str.count("ties=(Sigma=1.2"),1)
+        
+        # remove non-existent tie and test it has no effect
+        c.untie("f1.Height")
+        cu_str = str(c)
+        self.assertEqual(c_str, cu_str)
+        
+        # remove actual tie
+        c.untie("f1.Sigma")
+        cz_str = str(c)
+        self.assertEqual(cz_str.count("ties="),0)
+        
+    def test_fix_all(self):
+        g0 = FunctionWrapper( "Gaussian", Height=7.5, Sigma=1.2, PeakCentre=10)
+        g1 = FunctionWrapper( "Gaussian", Height=8.5, Sigma=1.2, PeakCentre=11)
+        c = CompositeFunctionWrapper(g0, g1)
+        
+        c.fixAll("Sigma")
+        c_str = str(c)
+        self.assertEqual(c_str.count("ties="),2)
+        self.assertEqual(c_str.count("ties=(Sigma="),2)
+        
+        # remove non-existent ties and test it has no effect
+        c.untieAll("Height")
+        cu_str = str(c)
+        self.assertEqual(c_str, cu_str)
+        
+        # remove actual ties
+        c.untieAll("Sigma")
+        cz_str = str(c)
+        self.assertEqual(cz_str.count("ties="),0)
+        
+    def test_fix_all_parameters(self):
+        g0 = FunctionWrapper( "Gaussian", Height=7.5, Sigma=1.2, PeakCentre=10)
+        g1 = FunctionWrapper( "Gaussian", Height=7.5, Sigma=1.2, PeakCentre=10)
+        c = CompositeFunctionWrapper(g0, g1)
+        
+        c.fixAllParameters()
+        c_str = str(c)
+        self.assertEqual(c_str.count("ties="),2)
+        self.assertEqual(c_str.count("ties=(Height=7.5,PeakCentre=10,Sigma=1.2)"),2)
+        
+        c.untieAllParameters()
+        cz_str = str(c)
+        self.assertEqual(cz_str.count("ties="),0)
+    
+
+    def test_tie(self):
+        g = FunctionWrapper( "Gaussian", Height=8.5, Sigma=1.2, PeakCentre=15)
+        
+        g.tie(Sigma="0.1*Height")
+        g_str = str(g)
+        self.assertEqual(g_str.count("ties="),1)
+        self.assertEqual(g_str.count("ties=(Sigma=0.1*Height)"),1)
+        
+        g0 = FunctionWrapper( "Gaussian", Height=7.5, Sigma=1.2, PeakCentre=10)
+        g1 = FunctionWrapper( "Gaussian", Height=8.5, Sigma=1.2, PeakCentre=11)
+        c = CompositeFunctionWrapper(g0, g1)
+        
+        c.tie({"f1.Sigma":"f0.Sigma"})
+        c_str = str(c)
+        self.assertEqual(c_str.count("ties="),1)
+        self.assertEqual(c_str.count("ties=(f1.Sigma=f0.Sigma)"),1)
+        
+        c.untie("f1.Sigma")
+        cz_str = str(c)
+        self.assertEqual(cz_str.count("ties="),0)
+        
+    def test_tie_all(self):
+    
+        g0 = FunctionWrapper( "Gaussian", Height=7.5, Sigma=1.2, PeakCentre=10)
+        g1 = FunctionWrapper( "Gaussian", Height=8.5, Sigma=1.2, PeakCentre=11)
+        c = CompositeFunctionWrapper(g0, g1)
+        
+        c.tieAll("Sigma")
+        c_str = str(c)
+        self.assertEqual(c_str.count("ties="),1)
+        self.assertEqual(c_str.count("ties=(f1.Sigma=f0.Sigma)"),1)
+        
+        c.untieAll("Sigma")
+        cz_str = str(c)
+        self.assertEqual(cz_str.count("ties="),0)
+        
+    def test_constrain(self):
+        g = FunctionWrapper( "Gaussian", Height=8.5, Sigma=1.2, PeakCentre=15)
+        
+        g.constrain("Sigma < 2.0, Height > 7.0")
+        g_str = str(g)
+        self.assertEqual(g_str.count("constraints="),1)
+        self.assertEqual(g_str.count("Sigma<2"),1)
+        self.assertEqual(g_str.count("7<Height"),1)
+
+        
+        g.unconstrain("Height")
+        g1_str = str(g)
+        self.assertEqual(g1_str.count("constraints="),1)
+        self.assertEqual(g1_str.count("constraints=(Sigma<2)"),1)
+        
+        g.unconstrain("Sigma")
+        gz_str = str(g)
+        self.assertEqual(gz_str.count("constraints="),0)
+        
+    def test_constrain_composite(self):
+        g0 = FunctionWrapper( "Gaussian", Height=7.5, Sigma=1.2, PeakCentre=10)
+        g1 = FunctionWrapper( "Gaussian", Height=8.5, Sigma=1.2, PeakCentre=11)
+        c = CompositeFunctionWrapper(g0, g1)
+        
+        c.constrain("f1.Sigma < 2, f0.Height > 7")
+        c_str = str(c)
+        self.assertEqual(c_str.count("f1.Sigma<2"),1)
+        self.assertEqual(c_str.count("7<f0.Height"),1)
+        
+        c.unconstrain("f1.Sigma")
+        c.unconstrain("f0.Height")
+        cz_str = str(c)
+        self.assertEqual(cz_str.count("Constraints="),0)
+        
+    def test_constrainall(self):
+        lb = FunctionWrapper("LinearBackground", A0=0.5, A1=1.5)
+        g0 = FunctionWrapper( "Gaussian", Height=7.5, Sigma=1.2, PeakCentre=10)
+        g1 = FunctionWrapper( "Gaussian", Height=8.5, Sigma=1.2, PeakCentre=11)
+        g2 = FunctionWrapper( "Gaussian", Height=9.5, Sigma=1.2, PeakCentre=12)
+        c0 = CompositeFunctionWrapper( g1, g2 )
+        c = CompositeFunctionWrapper( lb, g0, c0)
+        c.constrainAll("Sigma < 1.8")
+        
+        c_str = str(c)
+        self.assertEqual(c_str.count("constraints="),3)
+        self.assertEqual(c_str.count("Sigma<1.8"),3)
+        
+        lb_str = str(c[0])
+        self.assertEqual(lb_str.count("constraints="),0)
+        
+        g0_str = str(c[1])
+        self.assertEqual(g0_str.count("constraints="),1)
+        self.assertEqual(g0_str.count("Sigma<1.8"),1)
+        
+        g1_str = str(c[2][0])
+        self.assertEqual(g1_str.count("constraints="),1)
+        self.assertEqual(g1_str.count("Sigma<1.8"),1)
+        
+        g2_str = str(c[2][1])
+        self.assertEqual(g2_str.count("constraints="),1)
+        self.assertEqual(g2_str.count("Sigma<1.8"),1)
+        
+        c.unconstrainAll("Sigma")
+        
+        cz_str = str(c)
+        self.assertEqual(cz_str.count("constraints="),0)
+              
+    def test_free(self):
+        g = FunctionWrapper( "Gaussian", Height=8.5, Sigma=1.2, PeakCentre=15)
+        
+        g.constrain("Sigma < 2.0, Height > 7.0")
+        g.tie({"PeakCentre":"2*Height"})
+        
+        g.free("Height")
+        g1_str = str(g)
+        self.assertEqual(g1_str.count("ties="),1)
+        self.assertEqual(g1_str.count("constraints="),1)
+        self.assertEqual(g1_str.count("constraints=(Sigma<2)"),1)
+        
+        g.free("PeakCentre")
+        g2_str = str(g)
+        self.assertEqual(g2_str.count("ties="),0)
+        self.assertEqual(g2_str.count("constraints="),1)
+        
+        g.free("Sigma")
+        gz_str = str(g)
+        self.assertEqual(gz_str.count("constraints="),0)
+        
+    def test_pureaddition_and_puremultiplication(self):
+        g0 = FunctionWrapper( "Gaussian", Height=7.5, Sigma=1.2, PeakCentre=10)  
+        g1 = FunctionWrapper( "Gaussian", Height=8.5, Sigma=1.25, PeakCentre=12)
+        
+        sum = CompositeFunctionWrapper(g0, g1)
+        self.assertEqual(sum.pureAddition, True)
+        self.assertEqual(sum.pureMultiplication, False)
+        
+        product = ProductFunctionWrapper(g0, g1)
+        self.assertEqual(product.pureAddition, False)
+        self.assertEqual(product.pureMultiplication, True)
+        
+        sum2 = CompositeFunctionWrapper(sum, g1)
+        self.assertEqual(sum2.pureAddition, True)
+        self.assertEqual(sum2.pureMultiplication, False)
+        
+        product2 = ProductFunctionWrapper(product, g1)
+        self.assertEqual(product2.pureAddition, False)
+        self.assertEqual(product2.pureMultiplication, True)
+        
+        mixed1 = CompositeFunctionWrapper(product, g1)
+        self.assertEqual(mixed1.pureAddition, False)
+        self.assertEqual(mixed1.pureMultiplication, False)
+        
+        mixed2 = CompositeFunctionWrapper(g1, product)
+        self.assertEqual(mixed2.pureAddition, False)
+        self.assertEqual(mixed2.pureMultiplication, False)
+        
+        mixed3 = ProductFunctionWrapper(sum, g1)
+        self.assertEqual(mixed3.pureAddition, False)
+        self.assertEqual(mixed3.pureMultiplication, False)
+        
+        mixed4 = ProductFunctionWrapper(g1, sum)
+        self.assertEqual(mixed4.pureAddition, False)
+        self.assertEqual(mixed4.pureMultiplication, False)
+        
+    def test_flatten(self):
+        g0 = FunctionWrapper( "Gaussian", Height=7.5, Sigma=1.2, PeakCentre=10)  
+        g1 = FunctionWrapper( "Gaussian", Height=8.5, Sigma=1.25, PeakCentre=12)  
+        g2 = FunctionWrapper( "Gaussian", Height=9.5, Sigma=1.3, PeakCentre=14)
+        l = FunctionWrapper("Lorentzian",PeakCentre=9, Amplitude=2.4, FWHM=3)
+        lb = FunctionWrapper("LinearBackground")
+        
+        # Test already flat composite function, no change should occur
+        c1 = CompositeFunctionWrapper(lb, g0, g1 )
+        fc1 = c1.flatten()
+        c1_str = str(c1)
+        fc1_str = str(fc1)
+        self.assertEqual(fc1_str,c1_str)
+        
+        # Test composite function of depth 1
+        c2 = CompositeFunctionWrapper(c1, l)
+        fc2 = c2.flatten()
+        fc2_str = str(fc2)
+        self.assertEqual(fc2_str.count("("),0)
+        self.assertEqual(fc2_str.count("PeakCentre"),3)
+        self.assertEqual(fc2_str.count("Sigma="),2)
+        self.assertEqual(fc2_str.count("Sigma=1.25"),1)
+        
+        # Test composite function of depth 2
+        c3 = CompositeFunctionWrapper( g2, c2)
+        fc3 = c3.flatten()
+        fc3_str = str(fc3)
+        self.assertEqual(fc3_str.count("("),0)
+        self.assertEqual(fc3_str.count("PeakCentre"),4)
+        self.assertEqual(fc3_str.count("Sigma="),3)
+        self.assertEqual(fc3_str.count("Sigma=1.25"),1)
+        self.assertEqual(fc3_str.count("Sigma=1.3"),1)
+        
+        # Test product function of depth 1
+        p1 = ProductFunctionWrapper(lb, g0, g1 )
+        p2 = ProductFunctionWrapper(p1, l)
+        fp2 = p2.flatten()
+        self.assertTrue( isinstance (fp2, ProductFunctionWrapper))
+        fp2_str = str(fp2)
+        self.assertEqual(fp2_str.count("("),0)
+        self.assertEqual(fp2_str.count("PeakCentre"),3)
+        self.assertEqual(fp2_str.count("Sigma="),2)
+        self.assertEqual(fp2_str.count("Sigma=1.25"),1)
+        
+    def test_add(self):
+        g0 = FunctionWrapper( "Gaussian", Height=7.5, Sigma=1.2, PeakCentre=10)  
+        g1 = FunctionWrapper( "Gaussian", Height=8.5, Sigma=1.25, PeakCentre=12)  
+        lb = FunctionWrapper("LinearBackground")
+        
+        c = lb + g0 + g1
+        
+        self.assertTrue( isinstance( c, CompositeFunctionWrapper) )
+        c_str = str(c)
+        # self.assertEqual(c_str.count("("),0)
+        self.assertEqual(c_str.count("LinearBackground"),1)
+        self.assertEqual(c_str.count("Gaussian"),2)
+        
+        #lb_str = str(lb)
+        #c0_str = str(c[0])
+        #self.assertEqual(c0_str, lb_str)
+           
+        #g0_str = str(g0)
+        #c1_str = str(c[1])
+        #self.assertEqual(c1_str, g0_str)
+        
+        #g1_str = str(g1)
+        #c2_str = str(c[2])
+        #self.assertEqual(c2_str, g1_str)
+        
+    def test_incremental_add(self):
+        g0 = FunctionWrapper( "Gaussian", Height=7.5, Sigma=1.2, PeakCentre=10)  
+        g1 = FunctionWrapper( "Gaussian", Height=8.5, Sigma=1.25, PeakCentre=12)  
+        lb = FunctionWrapper("LinearBackground")
+        
+        c = CompositeFunctionWrapper( lb, g0)
+        c += g1
+        c_str = str(c)
+        self.assertEqual(c_str.count("("),0)
+        self.assertEqual(c_str.count("LinearBackground"),1)
+        self.assertEqual(c_str.count("Gaussian"),2)
+        
+        lb_str = str(lb)
+        c0_str = str(c[0])
+        self.assertEqual(c0_str, lb_str)
+           
+        g0_str = str(g0)
+        c1_str = str(c[1])
+        self.assertEqual(c1_str, g0_str)
+        
+        g1_str = str(g1)
+        c2_str = str(c[2])
+        self.assertEqual(c2_str, g1_str)
+
+    def test_del(self):
+        g0 = FunctionWrapper( "Gaussian", Height=7.5, Sigma=1.2, PeakCentre=10)  
+        g1 = FunctionWrapper( "Gaussian", Height=8.5, Sigma=1.25, PeakCentre=12)  
+        lb = FunctionWrapper("LinearBackground")
+        
+        c = CompositeFunctionWrapper( lb, g0, g1)
+        del c[1]
+        
+        c_str = str(c)
+        self.assertEqual(c_str.count("("),0)
+        self.assertEqual(c_str.count("LinearBackground"),1)
+        self.assertEqual(c_str.count("Gaussian"),1)
+        self.assertEqual(c_str.count("Height=8.5"),1)
+        
+    def test_len(self):
+        g0 = FunctionWrapper( "Gaussian", Height=7.5, Sigma=1.2, PeakCentre=10)  
+        g1 = FunctionWrapper( "Gaussian", Height=8.5, Sigma=1.25, PeakCentre=12)  
+        lb = FunctionWrapper("LinearBackground")
+        
+        c = CompositeFunctionWrapper( lb, g0, g1)
+        self.assertEqual( len(c), 3)
+        
+    def test_iteration(self):
+        g0 = FunctionWrapper( "Gaussian", Height=7.5, Sigma=1.2, PeakCentre=10)  
+        g1 = FunctionWrapper( "Gaussian", Height=8.5, Sigma=1.25, PeakCentre=12)  
+        lb = FunctionWrapper("LinearBackground")
+        
+        c = CompositeFunctionWrapper( lb, g0, g1)
+        count = 0
+        for f in c:
+           count += 1
+        self.assertEqual( count, 3)
+        
+    def test_productfunction_creation(self):
+        g0 = FunctionWrapper( "Gaussian", Height=7.5, Sigma=1.2, PeakCentre=10)
+        g1 = FunctionWrapper( "Gaussian", Height=8.5, Sigma=1.2, PeakCentre=11)
+        testhelpers.assertRaisesNothing(self, ProductFunctionWrapper, g0, g1)
+
+    def test_mul(self):
+        g0 = FunctionWrapper( "Gaussian", Height=7.5, Sigma=1.2, PeakCentre=10)  
+        g1 = FunctionWrapper( "Gaussian", Height=8.5, Sigma=1.25, PeakCentre=12)  
+        lb = FunctionWrapper("LinearBackground")
+        
+        p = lb * g0 * g1
+        
+        self.assertTrue( isinstance( p, ProductFunctionWrapper) )
+        p_str = str(p)
+        self.assertEqual(p_str.count("("),0)
+        self.assertEqual(p_str.count("LinearBackground"),1)
+        self.assertEqual(p_str.count("Gaussian"),2)
+        
+        lb_str = str(lb)
+        p0_str = str(p[0])
+        self.assertEqual(p0_str, lb_str)
+           
+        g0_str = str(g0)
+        p1_str = str(p[1])
+        self.assertEqual(p1_str, g0_str)
+        
+        g1_str = str(g1)
+        p2_str = str(p[2])
+        self.assertEqual(p2_str, g1_str)   
+
+    def test_convolution_creation(self):
+        g0 = FunctionWrapper( "Gaussian", Height=7.5, Sigma=1.2, PeakCentre=10)
+        g1 = FunctionWrapper( "Gaussian", Height=8.5, Sigma=1.2, PeakCentre=11)
+        testhelpers.assertRaisesNothing(self, ConvolutionWrapper, g0, g1)
+        
+    def test_multidomainfunction_creation(self):
+        g0 = FunctionWrapper( "Gaussian", Height=7.5, Sigma=1.2, PeakCentre=10)
+        g1 = FunctionWrapper( "Gaussian", Height=8.5, Sigma=1.2, PeakCentre=11)
+        testhelpers.assertRaisesNothing(self, MultiDomainFunctionWrapper, g0, g1)
+        m = MultiDomainFunctionWrapper( g0, g1, Global=["Height"])
+        self.assertEqual( m.nDomains, 2)
+        m_str = str(m)
+        self.assertEqual( m_str.count("ties"),1)
+        self.assertEqual( m_str.count("Height"),4) # 2 in functions 2 in ties
+        
+    def test_generatedfunction(self):
+        testhelpers.assertRaisesNothing(self, Gaussian, Height=7.5, Sigma=1.2, PeakCentre=10)
+        lb = LinearBackground()
+        g = Gaussian(Height=7.5, Sigma=1.2, PeakCentre=10)
+        s = lb + g
+        self.assertTrue( isinstance( s, CompositeFunctionWrapper) )
+    
+    def test_evaluation(self):
+        l0 = FunctionWrapper( "LinearBackground", A0=0, A1=2)
+        l1 = FunctionWrapper( "LinearBackground", A0=5, A1=-1)
+
+        ws = CreateWorkspace(DataX=[0,1,2,3,4], DataY=[5,5,5,5])
+        
+        c = CompositeFunctionWrapper(l0, l1)
+        cws = EvaluateFunction(c,"ws", OutputWorkspace='out')
+        cvals = cws.readY(1)
+        self.assertAlmostEqual(cvals[0], 5.5)
+        self.assertAlmostEqual(cvals[1], 6.5)
+        self.assertAlmostEqual(cvals[2], 7.5)
+        self.assertAlmostEqual(cvals[3], 8.5)
+        
+        p = ProductFunctionWrapper(l0, l1)
+        pws = EvaluateFunction(p,"ws", OutputWorkspace='out')
+        pvals = pws.readY(1)
+        self.assertAlmostEqual(pvals[0], 4.5)
+        self.assertAlmostEqual(pvals[1], 10.5)
+        self.assertAlmostEqual(pvals[2], 12.5)
+        self.assertAlmostEqual(pvals[3], 10.5)
+        
+        sq = Polynomial(attributes={'n': 2}, A0=0, A1=0.0, A2=1.0)
+        sqws = EvaluateFunction(sq,"ws", OutputWorkspace='out')
+        sqvals = sqws.readY(1)
+        self.assertAlmostEqual(sqvals[0], 0.25)
+        self.assertAlmostEqual(sqvals[1], 2.25)
+        self.assertAlmostEqual(sqvals[2], 6.25)
+        
+    def test_arithmetic(self):
+        l0 = FunctionWrapper( "LinearBackground", A0=0, A1=2)
+        l1 = FunctionWrapper( "LinearBackground", A0=5, A1=-1)
+
+        ws = CreateWorkspace(DataX=[0,1], DataY=[5])
+
+        c = CompositeFunctionWrapper(l0, l1)
+        p = ProductFunctionWrapper(l0, l1)
+        
+        s1 = c + p
+        s1ws = EvaluateFunction(s1,"ws", OutputWorkspace='out')
+        s1vals = s1ws.readY(1)
+        self.assertAlmostEqual(s1vals[0], 10.0)
+        
+        s2 = p + c
+        s2ws = EvaluateFunction(s2,"ws", OutputWorkspace='out')
+        s2vals = s2ws.readY(1)
+        self.assertAlmostEqual(s2vals[0], 10.0)
+
+        
+       
+if __name__ == '__main__':
+    unittest.main()
diff --git a/Framework/PythonInterface/test/python/mantid/SimpleAPIFitTest.py b/Framework/PythonInterface/test/python/mantid/SimpleAPIFitTest.py
index 0972a92591a..2d604d2f71f 100644
--- a/Framework/PythonInterface/test/python/mantid/SimpleAPIFitTest.py
+++ b/Framework/PythonInterface/test/python/mantid/SimpleAPIFitTest.py
@@ -6,7 +6,7 @@ from __future__ import (absolute_import, division, print_function)
 import unittest
 import testhelpers
 import platform
-from mantid.simpleapi import CreateWorkspace, Fit, FitDialog
+from mantid.simpleapi import CreateWorkspace, Fit, FitDialog, FunctionWrapper
 from mantid.api import mtd, MatrixWorkspace, ITableWorkspace
 import numpy as np
 from testhelpers import run_algorithm
@@ -26,6 +26,12 @@ class SimpleAPIFitTest(unittest.TestCase):
         if  platform.system() == 'Darwin': # crashes
             return
         testhelpers.assertRaisesNothing(self, Fit, "name=FlatBackground", self._raw_ws)
+        
+    def test_minimal_positional_arguments_with_functionwrapper_work(self):
+        if  platform.system() == 'Darwin': # crashes
+            return
+        fb = FunctionWrapper("FlatBackground")
+        testhelpers.assertRaisesNothing(self, Fit, fb, self._raw_ws)
 
     def test_function_positional_and_workspace_keyword_arguments_work(self):
         if  platform.system() == 'Darwin': # crashes
diff --git a/Framework/SINQ/src/PoldiPeakSearch.cpp b/Framework/SINQ/src/PoldiPeakSearch.cpp
index f6091db34bb..2b502b516b6 100644
--- a/Framework/SINQ/src/PoldiPeakSearch.cpp
+++ b/Framework/SINQ/src/PoldiPeakSearch.cpp
@@ -564,7 +564,7 @@ void PoldiPeakSearch::exec() {
 
   Unit_sptr xUnit = correlationWorkspace->getAxis(0)->unit();
 
-  if (xUnit->caption() == "") {
+  if (xUnit->caption().empty()) {
     g_log.information()
         << "   Workspace does not have unit, defaulting to MomentumTransfer.\n";
 
diff --git a/Framework/TestHelpers/src/FileComparisonHelper.cpp b/Framework/TestHelpers/src/FileComparisonHelper.cpp
index 5da64ccbeec..ccf783e80f2 100644
--- a/Framework/TestHelpers/src/FileComparisonHelper.cpp
+++ b/Framework/TestHelpers/src/FileComparisonHelper.cpp
@@ -112,7 +112,7 @@ bool areIteratorsEqual(streamCharIter refStream, streamCharIter testStream,
     Mantid::Kernel::Logger g_log("FileComparisonHelper");
     g_log.error("Length of both files were not identical");
     areStreamsEqual = false;
-  } else if (numNewLines == 0 && seenChars.size() == 0) {
+  } else if (numNewLines == 0 && seenChars.empty()) {
     Mantid::Kernel::Logger g_log("FileComparisonHelper");
     g_log.error("No characters checked in FileComparisonHelper");
     areStreamsEqual = false;
@@ -190,7 +190,7 @@ bool isEqualToReferenceFile(const std::string &referenceFileName,
   const std::string referenceFilePath =
       Mantid::API::FileFinder::Instance().getFullPath(referenceFileName);
 
-  if (referenceFilePath == "") {
+  if (referenceFilePath.empty()) {
     throw std::invalid_argument("No reference file with the name: " +
                                 referenceFileName +
                                 " could be found by FileComparisonHelper");
diff --git a/Framework/WorkflowAlgorithms/src/EQSANSLoad.cpp b/Framework/WorkflowAlgorithms/src/EQSANSLoad.cpp
index c5536b678ce..ca1966529fe 100644
--- a/Framework/WorkflowAlgorithms/src/EQSANSLoad.cpp
+++ b/Framework/WorkflowAlgorithms/src/EQSANSLoad.cpp
@@ -579,24 +579,44 @@ void EQSANSLoad::exec() {
       throw std::runtime_error("Could not cast (interpret) the property " +
                                dzName + " as a time series property value.");
     sfdd = dp->getStatistics().mean;
-    s2d = sfdd;
 
     // Modify SDD according to the DetectorDistance offset if given
     const double sampleflange_det_offset = getProperty("DetectorOffset");
     if (!isEmpty(sampleflange_det_offset))
       sfdd += sampleflange_det_offset;
 
-    // Modify S2D according to the SampleDistance offset if given
-    // This assumes that a positive offset moves the sample toward the detector
-    const double sampleflange_sample_offset = getProperty("SampleOffset");
-    if (!isEmpty(sampleflange_sample_offset))
-      s2d = s2d - sampleflange_sample_offset + sampleflange_det_offset;
-
-    // Modify SDD according to SampleDetectorDistanceOffset offset if given
+    // Modify SDD according to SampleDetectorDistanceOffset offset if given.
+    // This is here for backward compatibility.
     const double sample_det_offset =
         getProperty("SampleDetectorDistanceOffset");
     if (!isEmpty(sample_det_offset))
-      s2d += sample_det_offset;
+      sfdd += sample_det_offset;
+    if (!isEmpty(sample_det_offset) && !isEmpty(sampleflange_det_offset))
+      g_log.error() << "Both DetectorOffset and SampleDetectorDistanceOffset "
+                       "are set. Only one should be used.\n";
+
+    s2d = sfdd;
+    // Modify S2D according to the SampleDistance offset if given
+    // This assumes that a positive offset moves the sample toward the detector
+    const double sampleflange_sample_offset = getProperty("SampleOffset");
+    if (!isEmpty(sampleflange_sample_offset)) {
+      s2d -= sampleflange_sample_offset;
+
+      // Move the sample to its correct position
+      IAlgorithm_sptr mvAlg =
+          createChildAlgorithm("MoveInstrumentComponent", 0.2, 0.4);
+      mvAlg->setProperty<MatrixWorkspace_sptr>("Workspace", dataWS);
+      mvAlg->setProperty("ComponentName", "sample-position");
+      mvAlg->setProperty("Z", sampleflange_sample_offset / 1000.0);
+      mvAlg->setProperty("RelativePosition", false);
+      mvAlg->executeAsChildAlg();
+      g_log.information() << "Moving sample to "
+                          << sampleflange_sample_offset / 1000.0 << " meters\n";
+      m_output_message += "   Sample position: " +
+                          Poco::NumberFormatter::format(
+                              sampleflange_sample_offset / 1000.0, 3) +
+                          " m\n";
+    }
   }
   dataWS->mutableRun().addProperty("sampleflange_detector_distance", sfdd, "mm",
                                    true);
@@ -812,7 +832,6 @@ void EQSANSLoad::exec() {
                                    getPropertyValue("OutputWorkspace"), true);
   setProperty<MatrixWorkspace_sptr>(
       "OutputWorkspace", boost::dynamic_pointer_cast<MatrixWorkspace>(dataWS));
-  // m_output_message = "Loaded " + fileName + '\n' + m_output_message;
   setPropertyValue("OutputMessage", m_output_message);
 }
 
diff --git a/Testing/SystemTests/tests/analysis/EnggCalibrationTest.py b/Testing/SystemTests/tests/analysis/EnggCalibrationTest.py
index 51971cb3609..3adc980142a 100644
--- a/Testing/SystemTests/tests/analysis/EnggCalibrationTest.py
+++ b/Testing/SystemTests/tests/analysis/EnggCalibrationTest.py
@@ -1,6 +1,7 @@
 #pylint: disable=no-init
 import stresstesting
 from mantid.simpleapi import *
+from six import PY2
 
 
 def rel_err_less_delta(val, ref, epsilon):
@@ -234,7 +235,8 @@ class EnginXCalibrateFullThenCalibrateTest(stresstesting.MantidStressTest):
         for idx in [0, 12, 516, 789, 891, 1112]:
             cell_val = self.peaks_info.cell(idx,1)
             self.assertTrue(isinstance(cell_val, str))
-            self.assertEquals(cell_val[0:11], '{"1": {"A":')
+            if PY2: # This test depends on consistent ordering of a dict which is not guaranteed for python 3
+                self.assertEquals(cell_val[0:11], '{"1": {"A":')
             self.assertEquals(cell_val[-2:], '}}')
 
         self.assertEquals(self.difa, 0)
diff --git a/Testing/SystemTests/tests/analysis/LoadLotsOfFiles.py b/Testing/SystemTests/tests/analysis/LoadLotsOfFiles.py
index e9faa5b9a02..542abbf0f7a 100644
--- a/Testing/SystemTests/tests/analysis/LoadLotsOfFiles.py
+++ b/Testing/SystemTests/tests/analysis/LoadLotsOfFiles.py
@@ -106,7 +106,8 @@ BANNED_FILES = ['80_tubes_Top_and_Bottom_April_2015.xml',
                 'TolueneLargerOrderAbins.out',
                 'TolueneScale.out',
                 'TolueneScratchAbins.out',
-                'SingleCrystalDiffuseReduction_UB.mat'
+                'SingleCrystalDiffuseReduction_UB.mat',
+                'Na2SiF6_DMOL3.outmol'
                 ]
 
 EXPECTED_EXT = '.expected'
diff --git a/docs/source/algorithms/AbsorptionCorrection-v1.rst b/docs/source/algorithms/AbsorptionCorrection-v1.rst
index 471c597be76..8a79dfef66b 100644
--- a/docs/source/algorithms/AbsorptionCorrection-v1.rst
+++ b/docs/source/algorithms/AbsorptionCorrection-v1.rst
@@ -21,6 +21,9 @@ calculated for the centre-point of each element, and a numerical
 integration is carried out using these path lengths over the volume
 elements.
 
+The output workspace will contain the attenuation factors. To apply
+the correction you must divide your data set by the resulting factors.
+
 Note that the duration of this algorithm is strongly dependent on the
 element size chosen, and that too small an element size can cause the
 algorithm to fail because of insufficient memory.
@@ -75,12 +78,12 @@ Usage
 **Example: A simple spherical sample**
 
 .. testcode:: ExSimpleSpere
-    
+
     #setup the sample shape
     sphere = '''<sphere id="sample-sphere">
-          <centre x="0" y="0" z="0"/>
-          <radius val="0.1" />
-      </sphere>'''
+    <centre x="0" y="0" z="0"/>
+    <radius val="0.1" />
+    </sphere>'''
 
     ws = CreateSampleWorkspace("Histogram",NumBanks=1,BankPixelWidth=1)
     ws = ConvertUnits(ws,"Wavelength")
@@ -90,14 +93,21 @@ Usage
 
     #restrict the number of wavelength points to speed up the example
     wsOut = AbsorptionCorrection(ws, NumberOfWavelengthPoints=5, ElementSize=3)
+    wsCorrected = ws / wsOut
 
     print "The created workspace has one entry for each spectra: %i" % wsOut.getNumberHistograms()
+    print "Original y values: ", ws.readY(0)
+    print "Corrected y values: ", wsCorrected.readY(0)
 
 Output:
 
 .. testoutput:: ExSimpleSpere
 
     The created workspace has one entry for each spectra: 1
+    Original y values:  [  5.68751434   5.68751434  15.68751434   5.68751434   5.68751434
+       1.56242829]
+    Corrected y values:  [   818.30188422   2375.96165593  14211.52238463   9472.21381511
+      15718.79220638   5747.16759791]
 
 .. categories::
 
diff --git a/docs/source/concepts/FitFunctionsInPython.rst b/docs/source/concepts/FitFunctionsInPython.rst
new file mode 100644
index 00000000000..eec35460f98
--- /dev/null
+++ b/docs/source/concepts/FitFunctionsInPython.rst
@@ -0,0 +1,170 @@
+.. _FitFunctionsInPython:
+
+Fit Functions In Python
+=======================
+
+Introduction
+------------
+
+Mantid enables Fit function objects to be produced in python. For example
+
+.. code:: python
+
+  g = Gaussian()
+
+will make ``g`` into a Gaussian function with default values and
+
+.. code:: python
+
+  g = Gaussian(Height=1, Sigma=0.1)
+  
+will make ``g`` into a Gaussian function with ``Height`` set to 1, ``Sigma`` set to 0.1 and ``PeakCentre`` set to default value.
+
+One can also make function with attributes such as
+
+.. code:: python
+
+  p = Polynomial(n=3, A0=1, A1=2, A2=4, A3=3)
+  
+One can get and set function parameters and attributes, by array operations, for example:
+
+.. code:: python
+
+  gSigma = g["Sigma"]
+  g["Height"] = 1.5
+  
+One can also get and set function parameters and attributes, by dot operations, for example:
+
+.. code:: python
+
+  gSigma = g.Sigma
+  g.Height = 1.5
+  
+Composite Functions
+-------------------
+Composite functions can be created by means of the plus operation and 
+individual functions can be accessed by array index operations. 
+For example:
+
+.. code:: python
+
+   spectrum = LinearBackground() + Gaussian(PeakCentre=1) + Gaussian(PeakCentre=2)
+   peak1 = spectrum[1]
+   peak1['Sigma'] = 0.123
+   spectrum[2] = Lorentzian(PeakCentre=2)
+
+which sets ``Sigma`` of the second function (first Gaussian) to 0.123 and changes the third function to a Lorentzian.
+
+Similarly product functions can be constructed using the multiplication operator.
+
+.. code:: python
+
+   p = ExpDecay() * Gaussian(Height=1,Sigma=0.2)
+
+One can get and set the parameters of a composite function, by array operations, 
+but not by dot operations because the parameter name already contains a dot, for example:
+
+.. code:: python
+
+   f1Sigma = spectrum["f1.Sigma"]
+   spectrum["f1.Height"] = 1.5
+  
+One can add a function by the ``+-`` operator or remove be the ``del`` function.
+
+.. code:: python
+
+   spectrum += Lorentzian(PeakCentre=3)
+   del spectrum[1]
+   
+Also available is the ``len`` function and iteration over the member functions:
+
+.. code:: python
+
+   n_peaks = len(spectrum)
+   for func in spectrum: 
+      print(func) 
+      
+The plus and times operators are associative and 
+so may not preserve a composite function within a composite function as such,
+but replace it with a list of its member functions.
+Instead you may use:
+
+.. code:: python
+
+   spectrum = CompositeFunctionWrapper(LinearBackground(), Gaussian(PeakCentre=1), Gaussian(PeakCentre=2))
+   P = ProductFunctionWrapper(ExpDecay(), Gaussian(Height=1,Sigma=0.2))
+   
+Multi-Domain Functions
+----------------------
+Multi-Domain functions can be constructed like this:
+
+.. code:: python
+
+    md_fun = MultiDomainFunction(Gaussian(PeakCentre=1, Sigma=0.1), Gaussian(PeakCentre=1, Sigma=0.2), ..., global=['Height'])
+
+Setting Ties
+------------
+The parameters of functions can be tied or fixed like this:
+
+.. code:: python
+
+    func1.tie(A0=2.0)
+    func2.tie({'f1.A2': '2*f0.A1', 'f2.A2': '3*f0.A1 + 1'})
+    func3.fix('A0')
+    func4.fix('f2.A2')
+
+Both fixes and ties can be removed by ``untie``:
+
+.. code:: python
+
+    func.untie('f3.Sigma')
+    
+To tie all parameters of the same local name in a composite function, one can use ``TieAll``:
+
+.. code:: python
+
+    func.tieAll('Sigma')
+ 
+All members of the composite function must have this parameter (in this case ``Sigma``).
+Similarly with fixing:
+
+.. code:: python
+
+    spectrum1.fixAll('FWHM')
+    
+Also parameters of a function can be fixed with ``fixAllParameters`` and unfixed with ``untieAllParameters``.
+
+.. code:: python
+
+    c.fixAllParameters()
+    ... 
+    c.untieAllParameters()
+
+
+Setting Constraints
+------------------- 
+One can set and remove constraints as follows:
+
+.. code:: python
+
+    g.constrain("Sigma < 2.0, Height > 7.0") 
+    ...
+    g.unconstrain("Sigma")
+    g.unconstrain("Height")
+            
+    comp.constrain("f1.Sigma < 2, f0.Height > 7") 
+    ...
+    comp.unconstrain("f1.Sigma")
+    comp.unconstrain("f0.Height") 
+
+One can all constrain a given parameter in all members of a composite function that have this parameter 
+and also remove such constraints.
+
+.. code:: python
+
+    comp.constrainAll("Sigma < 1.8")
+    ...
+    comp.unconstrainAll("Sigma")
+    
+
+.. categories:: Concepts
diff --git a/docs/source/release/v3.11.0/diffraction.rst b/docs/source/release/v3.11.0/diffraction.rst
index 3bd30339ef1..88951c92c9d 100644
--- a/docs/source/release/v3.11.0/diffraction.rst
+++ b/docs/source/release/v3.11.0/diffraction.rst
@@ -22,9 +22,9 @@ Powder Diffraction
 - :ref:`LoadILLDiffraction <algm-LoadILLDiffraction>` now supports loading D2B data with detector scans. The D2B IDF has been updated, as previously it contained some errors in the positions of the tubes and size of the pixels.
 - :ref:`AlignAndFocusPowder <algm-AlignAndFocusPowder>` now correctly supports overloading the grouping file in the presence of a masking workspace.
 - :ref:`PDCalibration <algm-PDCalibration>` has changed how it calculates constants from peak positions to use a simplex optimization rather than Gauss-Markov method.
+- :ref:`ResampleX <algm-ResampleX>` has a bug fix in how it automatically determines the data range for a workspace with multiple spectra.
 - The powder diffraction GUI has had numerous bugfixes and now has an option to override the detector grouping.
 
-
 Single Crystal Diffraction
 --------------------------
 
diff --git a/docs/source/release/v3.11.0/framework.rst b/docs/source/release/v3.11.0/framework.rst
index e96671623b7..5b5b641c130 100644
--- a/docs/source/release/v3.11.0/framework.rst
+++ b/docs/source/release/v3.11.0/framework.rst
@@ -104,6 +104,8 @@ Python Fit Functions
 ####################
 
 - A bug that makes it difficult to define and use attributes in python fit functions has been fixed.
+- The usability of the fit functions has been improved, enabling users to construct and modify the functions as objects rather than strings 
+  as described :ref:`here <FitFunctionsInPython>`.
 
 |
 
diff --git a/docs/source/release/v3.11.0/indirect_inelastic.rst b/docs/source/release/v3.11.0/indirect_inelastic.rst
index f6f3e78edca..1ee658911c8 100644
--- a/docs/source/release/v3.11.0/indirect_inelastic.rst
+++ b/docs/source/release/v3.11.0/indirect_inelastic.rst
@@ -38,6 +38,21 @@ Bugfixes
 - Correct treatment of the resolution function: convolve sample and resolution spectra with same momentum transfer.
 - Property to pass the workspace index added to :ref:`algm-ConvolutionFitSequential`.
 
+Elwin
+~~~~~
+
+Bugfixes
+--------
+- Save Result now writes to file the temperature-dependent elastic intensity normalized to the lowest temperature.
+
+ConvFit
+~~~~~~~
+
+Bugfixes
+--------
+- Correct treatment of the resolution function: convolve sample and resolution spectra with same momentum transfer.
+- Property to pass the workspace index added to :ref:`algm-ConvolutionFitSequential`.
+
 Jump Fit
 ~~~~~~~~
 
diff --git a/docs/source/release/v3.11.0/reflectometry.rst b/docs/source/release/v3.11.0/reflectometry.rst
index 709724662d8..57ba862cb5c 100644
--- a/docs/source/release/v3.11.0/reflectometry.rst
+++ b/docs/source/release/v3.11.0/reflectometry.rst
@@ -11,6 +11,7 @@ Algorithms
 - The following bugs have been fixed in the summation in Q functionality in :ref:`algm-ReflectometryReductionOne`:
   - the incorrect angle was being used in the final conversion to Q in the divergent beam case
   - the input was being cropped, causing loss of counts
+  - summation in Q was giving incorrect results for a point detector
 - A new property, ``Diagnostics``, has been added to :ref:`algm-ReflectometryReductionOne` to allow the output of additional interim workspaces for debug purposes.
 
 Reflectometry Reduction Interface
diff --git a/docs/source/release/v3.11.0/sans.rst b/docs/source/release/v3.11.0/sans.rst
index cdcd6e4947a..8b75217d5b1 100644
--- a/docs/source/release/v3.11.0/sans.rst
+++ b/docs/source/release/v3.11.0/sans.rst
@@ -16,6 +16,9 @@ Bug Fixes
 
 - Displaying the masked workspace in the mask tab now makes sure that the masks are displayed in a grey color.
 
-|
+EQSANS
+------
+
+- Following hardware changes in the instrument, sample and detector offsets were added as parameters of the reduction.
 
 `Full list of changes on github <http://github.com/mantidproject/mantid/pulls?q=is%3Apr+milestone%3A%22Release+3.11%22+is%3Amerged+label%3A%22Component%3A+SANS%22>`__
diff --git a/qt/paraview_ext/PVPlugins/Representations/AlignedThreeSliceFilter.cxx b/qt/paraview_ext/PVPlugins/Representations/AlignedThreeSliceFilter.cxx
index 703d923eb41..ef1b8714bff 100644
--- a/qt/paraview_ext/PVPlugins/Representations/AlignedThreeSliceFilter.cxx
+++ b/qt/paraview_ext/PVPlugins/Representations/AlignedThreeSliceFilter.cxx
@@ -18,7 +18,7 @@
 #include "vtkAppendPolyData.h"
 #include "vtkPlane.h"
 
-#include <math.h>
+#include <cmath>
 
 vtkStandardNewMacro(AlignedThreeSliceFilter);
 
diff --git a/qt/paraview_ext/PVPlugins/Representations/vtkAlignedGeometrySliceRepresentation.cxx b/qt/paraview_ext/PVPlugins/Representations/vtkAlignedGeometrySliceRepresentation.cxx
index 5edce31a5ca..f7230cb6108 100644
--- a/qt/paraview_ext/PVPlugins/Representations/vtkAlignedGeometrySliceRepresentation.cxx
+++ b/qt/paraview_ext/PVPlugins/Representations/vtkAlignedGeometrySliceRepresentation.cxx
@@ -85,7 +85,7 @@ public:
     return true;
   }
   static bool ExtractCachedBounds(vtkDataObject *dataObject, double bounds[6]) {
-    if (dataObject == NULL || dataObject->GetFieldData() == NULL) {
+    if (dataObject == nullptr || dataObject->GetFieldData() == nullptr) {
       return false;
     }
     vtkFieldData *fd = dataObject->GetFieldData();
@@ -108,9 +108,8 @@ public:
   static vtkGSRGeometryFilter *New();
   vtkTypeMacro(vtkGSRGeometryFilter, vtkPVGeometryFilter);
 
-  virtual int RequestData(vtkInformation *req,
-                          vtkInformationVector **inputVector,
-                          vtkInformationVector *outputVector) VTK_OVERRIDE {
+  int RequestData(vtkInformation *req, vtkInformationVector **inputVector,
+                  vtkInformationVector *outputVector) VTK_OVERRIDE {
     vtkSmartPointer<vtkDataObject> inputDO =
         vtkDataObject::GetData(inputVector[0]);
     vtkSmartPointer<vtkMatrix4x4> changeOfBasisMatrix =
@@ -165,7 +164,7 @@ public:
 protected:
   vtkGSRGeometryFilter() {}
 
-  virtual ~vtkGSRGeometryFilter() {}
+  ~vtkGSRGeometryFilter() override {}
 
 private:
   vtkGSRGeometryFilter(const vtkGSRGeometryFilter &);
@@ -214,7 +213,7 @@ void vtkAlignedGeometrySliceRepresentation::SetupDefaults() {
       this->Internals->OutlineSource->GetOutputPort());
   this->Internals->OutlineActor->SetMapper(
       this->Internals->OutlineMapper.GetPointer());
-  this->Internals->OutlineActor->SetUseBounds(0);
+  this->Internals->OutlineActor->SetUseBounds(false);
   this->Internals->OutlineActor->SetVisibility(0);
 }
 
diff --git a/qt/paraview_ext/PVPlugins/Representations/vtkAlignedGeometrySliceRepresentation.h b/qt/paraview_ext/PVPlugins/Representations/vtkAlignedGeometrySliceRepresentation.h
index 1e24e767c3b..9bfad8b3434 100644
--- a/qt/paraview_ext/PVPlugins/Representations/vtkAlignedGeometrySliceRepresentation.h
+++ b/qt/paraview_ext/PVPlugins/Representations/vtkAlignedGeometrySliceRepresentation.h
@@ -35,9 +35,9 @@ public:
                vtkGeometryRepresentation);
   void PrintSelf(ostream &os, vtkIndent indent) VTK_OVERRIDE;
 
-  virtual int ProcessViewRequest(vtkInformationRequestKey *request_type,
-                                 vtkInformation *inInfo,
-                                 vtkInformation *outInfo) VTK_OVERRIDE;
+  int ProcessViewRequest(vtkInformationRequestKey *request_type,
+                         vtkInformation *inInfo,
+                         vtkInformation *outInfo) VTK_OVERRIDE;
 
   enum { X_SLICE_ONLY, Y_SLICE_ONLY, Z_SLICE_ONLY, ALL_SLICES };
   vtkSetClampMacro(Mode, int, X_SLICE_ONLY, ALL_SLICES);
@@ -53,15 +53,14 @@ public:
 
 protected:
   vtkAlignedGeometrySliceRepresentation();
-  ~vtkAlignedGeometrySliceRepresentation();
+  ~vtkAlignedGeometrySliceRepresentation() override;
 
-  virtual void SetupDefaults() VTK_OVERRIDE;
-  virtual int RequestData(vtkInformation *request,
-                          vtkInformationVector **inputVector,
-                          vtkInformationVector *outputVector) VTK_OVERRIDE;
+  void SetupDefaults() VTK_OVERRIDE;
+  int RequestData(vtkInformation *request, vtkInformationVector **inputVector,
+                  vtkInformationVector *outputVector) VTK_OVERRIDE;
 
-  virtual bool AddToView(vtkView *view) VTK_OVERRIDE;
-  virtual bool RemoveFromView(vtkView *view) VTK_OVERRIDE;
+  bool AddToView(vtkView *view) VTK_OVERRIDE;
+  bool RemoveFromView(vtkView *view) VTK_OVERRIDE;
 
 private:
   vtkAlignedGeometrySliceRepresentation(
diff --git a/qt/paraview_ext/VatesAPI/src/EventNexusLoadingPresenter.cpp b/qt/paraview_ext/VatesAPI/src/EventNexusLoadingPresenter.cpp
index 72f9f932108..301e885bcd6 100644
--- a/qt/paraview_ext/VatesAPI/src/EventNexusLoadingPresenter.cpp
+++ b/qt/paraview_ext/VatesAPI/src/EventNexusLoadingPresenter.cpp
@@ -44,7 +44,7 @@ EventNexusLoadingPresenter::EventNexusLoadingPresenter(
  */
 bool EventNexusLoadingPresenter::canReadFile() const {
   if (!canLoadFileBasedOnExtension(m_filename, ".nxs")) {
-    return 0;
+    return false;
   }
 
   std::unique_ptr<NeXus::File> file;
@@ -55,7 +55,7 @@ bool EventNexusLoadingPresenter::canReadFile() const {
       file->openGroup("entry", "NXentry");
     } catch (::NeXus::Exception &) {
       file->close();
-      return 0;
+      return false;
     }
     // But only eventNexus files have bank123_events as a group name
     std::map<std::string, std::string> entries = file->getEntries();
@@ -74,7 +74,7 @@ bool EventNexusLoadingPresenter::canReadFile() const {
     if (file)
       file->close();
   }
-  return 0;
+  return false;
 }
 
 /*
diff --git a/qt/paraview_ext/VatesAPI/src/MDEWEventNexusLoadingPresenter.cpp b/qt/paraview_ext/VatesAPI/src/MDEWEventNexusLoadingPresenter.cpp
index 81cc8b7afd0..02d1db6ebf5 100644
--- a/qt/paraview_ext/VatesAPI/src/MDEWEventNexusLoadingPresenter.cpp
+++ b/qt/paraview_ext/VatesAPI/src/MDEWEventNexusLoadingPresenter.cpp
@@ -43,19 +43,19 @@ is attempted to be loaded.
 bool MDEWEventNexusLoadingPresenter::canReadFile() const {
   // Quick check based on extension.
   if (!canLoadFileBasedOnExtension(m_filename, ".nxs")) {
-    return 0;
+    return false;
   }
 
   auto file = Kernel::make_unique<::NeXus::File>(this->m_filename);
   // MDEventWorkspace file has a different name for the entry
   try {
     file->openGroup("MDEventWorkspace", "NXentry");
-    return 1;
+    return true;
   } catch (::NeXus::Exception &) {
     // If the entry name does not match, then it can't read the file.
-    return 0;
+    return false;
   }
-  return 0;
+  return false;
 }
 
 /*
diff --git a/qt/paraview_ext/VatesAPI/src/MDHWNexusLoadingPresenter.cpp b/qt/paraview_ext/VatesAPI/src/MDHWNexusLoadingPresenter.cpp
index e8654c3c990..92eaa9ad25f 100644
--- a/qt/paraview_ext/VatesAPI/src/MDHWNexusLoadingPresenter.cpp
+++ b/qt/paraview_ext/VatesAPI/src/MDHWNexusLoadingPresenter.cpp
@@ -43,18 +43,18 @@ MDHWNexusLoadingPresenter::MDHWNexusLoadingPresenter(
 bool MDHWNexusLoadingPresenter::canReadFile() const {
   // Quick check based on extension.
   if (!canLoadFileBasedOnExtension(m_filename, ".nxs")) {
-    return 0;
+    return false;
   }
   auto file = Kernel::make_unique<::NeXus::File>(this->m_filename);
   // MDHistoWorkspace file has a different name for the entry
   try {
     file->openGroup("MDHistoWorkspace", "NXentry");
-    return 1;
+    return true;
   } catch (::NeXus::Exception &) {
     // If the entry name does not match, then it can't read the file.
-    return 0;
+    return false;
   }
-  return 0;
+  return false;
 }
 
 /**
diff --git a/qt/paraview_ext/VatesSimpleGui/ViewWidgets/src/pqCameraReactionNonOrthogonalAxes.cpp b/qt/paraview_ext/VatesSimpleGui/ViewWidgets/src/pqCameraReactionNonOrthogonalAxes.cpp
index 75b67ee3797..bf37322877c 100644
--- a/qt/paraview_ext/VatesSimpleGui/ViewWidgets/src/pqCameraReactionNonOrthogonalAxes.cpp
+++ b/qt/paraview_ext/VatesSimpleGui/ViewWidgets/src/pqCameraReactionNonOrthogonalAxes.cpp
@@ -93,7 +93,7 @@ void pqCameraReactionNonOrthogonalAxes::updateEnableState() {
     this->parentAction()->setEnabled(true);
   } else if (rview) {
     if (this->ReactionMode == ZOOM_TO_DATA) {
-      this->parentAction()->setEnabled(source != 0);
+      this->parentAction()->setEnabled(source != nullptr);
     } else {
       // Check hints to see if actions should be disabled
       bool cameraResetButtonsEnabled = true;
diff --git a/qt/paraview_ext/VatesSimpleGui/ViewWidgets/src/pqCameraToolbarNonOrthogonalAxes.cpp b/qt/paraview_ext/VatesSimpleGui/ViewWidgets/src/pqCameraToolbarNonOrthogonalAxes.cpp
index 1b9410c7dff..9482ec1aa71 100644
--- a/qt/paraview_ext/VatesSimpleGui/ViewWidgets/src/pqCameraToolbarNonOrthogonalAxes.cpp
+++ b/qt/paraview_ext/VatesSimpleGui/ViewWidgets/src/pqCameraToolbarNonOrthogonalAxes.cpp
@@ -68,7 +68,7 @@ void pqCameraToolbarNonOrthogonalAxes::constructor() {
 
   this->ZoomToDataAction = ui.actionZoomToData;
   this->ZoomToDataAction->setEnabled(
-      pqActiveObjects::instance().activeSource() != 0);
+      pqActiveObjects::instance().activeSource() != nullptr);
 
   QObject::connect(&pqActiveObjects::instance(), SIGNAL(viewChanged(pqView *)),
                    this, SLOT(updateEnabledState()));
diff --git a/scripts/Inelastic/CrystalField/CrystalFieldMultiSite.py b/scripts/Inelastic/CrystalField/CrystalFieldMultiSite.py
index ff4d2aeb490..bf457812eed 100644
--- a/scripts/Inelastic/CrystalField/CrystalFieldMultiSite.py
+++ b/scripts/Inelastic/CrystalField/CrystalFieldMultiSite.py
@@ -28,6 +28,8 @@ class CrystalFieldMultiSite(object):
         self._makeFunction(Ions, Symmetries)
         self.Ions = Ions
         self.Symmetries = Symmetries
+        self._plot_window = {}
+        self.chi2 = None
         parameter_dict = None
         attribute_dict = None
 
@@ -166,18 +168,55 @@ class CrystalFieldMultiSite(object):
         """Form a definition string for the CrystalFieldSpectrum function
         @param i: Index of a spectrum.
         """
-        funs = self.function.createEquivalentFunctions()
-        return str(funs[i])
+        if self.NumberOfSpectra == 1:
+            return str(self.function)
+        else:
+            funs = self.function.createEquivalentFunctions()
+            return str(funs[i])
 
     def update(self, func):
         """
         Update values of the fitting parameters.
         @param func: A IFunction object containing new parameter values.
         """
-        if self.NumberOfSpectra == 1:
-            return str(self.function)
+        self.function = func
+
+    def plot(self, i=0, workspace=None, ws_index=0, name=None):
+        """Plot a spectrum. Parameters are the same as in getSpectrum(...)"""
+        from mantidplot import plotSpectrum
+        from mantid.api import AlgorithmManager
+        createWS = AlgorithmManager.createUnmanaged('CreateWorkspace')
+        createWS.initialize()
+
+        xArray, yArray = self.getSpectrum(i, workspace, ws_index)
+        ws_name = name if name is not None else 'CrystalFieldMultiSite_%s' % self.Ions
+
+        if isinstance(i, int):
+            if workspace is None:
+                if i > 0:
+                    ws_name += '_%s' % i
+                createWS.setProperty('DataX', xArray)
+                createWS.setProperty('DataY', yArray)
+                createWS.setProperty('OutputWorkspace', ws_name)
+                createWS.execute()
+                plot_window = self._plot_window[i] if i in self._plot_window else None
+                self._plot_window[i] = plotSpectrum(ws_name, 0, window=plot_window, clearWindow=True)
+            else:
+                ws_name += '_%s' % workspace
+                if i > 0:
+                    ws_name += '_%s' % i
+                createWS.setProperty('DataX', xArray)
+                createWS.setProperty('DataY', yArray)
+                createWS.setProperty('OutputWorkspace', ws_name)
+                createWS.execute()
+                plotSpectrum(ws_name, 0)
         else:
-            self.function = func
+            ws_name += '_%s' % i
+            createWS.setProperty('DataX', xArray)
+            createWS.setProperty('DataY', yArray)
+            createWS.setProperty('OutputWorkspace', ws_name)
+            createWS.execute()
+            plotSpectrum(ws_name, 0)
 
     @property
     def Ions(self):
@@ -284,3 +323,39 @@ class CrystalFieldMultiSite(object):
     def NPeaks(self, value):
         self.function.setAttributeValue('NPeaks', value)
 
+    def fix(self, *args):
+        for a in args:
+            self.function.fixParameter(a)
+
+    def __getitem__(self, item):
+        if self.function.hasAttribute(item):
+            return self.function.getAttributeValue(item)
+        else:
+            return self.function.getParameterValue(item)
+
+
+    def setBackground(self, *args, peak=None, other=None):
+
+        if len(args) > 0:
+            bg = FunctionFactory.createFunction(args[0])
+            if isComposite(bg):
+                f0 = bg[0]
+                if isPeak(f0):
+                    peak = f0
+            return
+
+        self._background = Function(self.function, prefix='bg.')
+        if peak and other:
+            self._background.peak = Function(self.function, prefix='bg.f0.')
+            self._background.background = Function(self.function, prefix='bg.f1.')
+            self.function.setAttributeValue('Background', '%s;%s' % (peak, other))
+        elif peak:
+            self.function.setAttributeValue('Background', '%s' % peak)
+        elif other:
+            self.function.setAttributeValue('Background', '%s' % other)
+        else:
+            raise RuntimeError('!!!')
+
+    @property
+    def background(self):
+        return self._background
\ No newline at end of file
diff --git a/scripts/Inelastic/CrystalField/fitting.py b/scripts/Inelastic/CrystalField/fitting.py
index 89262a88cba..dfb66e9828e 100644
--- a/scripts/Inelastic/CrystalField/fitting.py
+++ b/scripts/Inelastic/CrystalField/fitting.py
@@ -1443,7 +1443,11 @@ class CrystalFieldFit(object):
         Fit when the model has multiple spectra.
         """
         from mantid.api import AlgorithmManager
-        fun = self.model.makeMultiSpectrumFunction()
+        from CrystalField.CrystalFieldMultiSite import CrystalFieldMultiSite
+        if isinstance(self.model, CrystalFieldMultiSite):
+            fun = str(self.model.function)
+        else:
+            fun = self.model.makeMultiSpectrumFunction()
         if 'CrystalFieldMultiSpectrum' in fun:
             fun = re.sub(r'(name=.*?,)(.*?)(PhysicalProperties=\(.*?\),)',r'\1\3\2', fun)
         alg = AlgorithmManager.createUnmanaged('Fit')
diff --git a/scripts/reduction_workflow/instruments/sans/sns_command_interface.py b/scripts/reduction_workflow/instruments/sans/sns_command_interface.py
index 5af57ec2241..8c8ba289f48 100644
--- a/scripts/reduction_workflow/instruments/sans/sns_command_interface.py
+++ b/scripts/reduction_workflow/instruments/sans/sns_command_interface.py
@@ -125,3 +125,11 @@ def Resolution(sample_aperture_diameter=10.0):
 
 def IndependentBinning(independent_binning=True):
     ReductionSingleton().reduction_properties["IQIndependentBinning"]=independent_binning
+
+
+def SetDetectorOffset(distance):
+    ReductionSingleton().reduction_properties["DetectorOffset"] = distance
+
+
+def SetSampleOffset(distance):
+    ReductionSingleton().reduction_properties["SampleOffset"] = distance
diff --git a/scripts/test/CrystalFieldMultiSiteTest.py b/scripts/test/CrystalFieldMultiSiteTest.py
index 6be9d3ff2a9..cffc8c7de54 100644
--- a/scripts/test/CrystalFieldMultiSiteTest.py
+++ b/scripts/test/CrystalFieldMultiSiteTest.py
@@ -13,7 +13,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
         self.assertEqual(cfms.Temperatures, [20, 52])
         self.assertEqual(cfms.FWHMs, [1.0, 1.0])
 
-    def test_init_multiple_ions(self):
+    def test_init_multi_ions(self):
         cfms = CrystalFieldMultiSite(Ions=('Pm', 'Eu'), Symmetries=('D2', 'C3v'), Temperatures=[20, 52],
                                      FWHMs=[1.0, 1.0])
         self.assertEqual(cfms.Ions, ['Pm', 'Eu'])
@@ -51,7 +51,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
         self.assertEqual(cfms.function.getParameterValue('BmolX'), 1.0)
         self.assertEqual(cfms.function.getParameterValue('B40'), -0.02)
 
-    def test_init_parameters_multiple_ions(self):
+    def test_init_parameters_multi_ions(self):
         cfms = CrystalFieldMultiSite(Ions=('Pm', 'Eu'), Symmetries=('D2', 'C3v'), Temperatures=[20, 52],
                                      FWHMs=[1.0, 1.0], parameters={'ion0.B40': -0.02, 'ion0.B42': -0.11,
                                                                    'ion1.B42': -0.12})
@@ -73,7 +73,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
         self.assertAlmostEqual(cfms.function.getParameterValue('pk0.Sigma'), 0.42, 2)
         self.assertEqual(cfms.function.getParameterValue('pk0.PeakCentre'), 0)
 
-    def test_peak_values_multiple_ions(self):
+    def test_peak_values_multi_ions(self):
         cfms = CrystalFieldMultiSite(Ions=('Pr', 'Nd'), Symmetries=('D2', 'C3v'), Temperatures=[20],
                                      FWHMs=[1.5], parameters={'ion0.B60': -0.02, 'ion1.B62': -0.12})
         self.assertEqual(int(cfms.function.getParameterValue('ion0.pk0.Amplitude')), 278)
@@ -83,7 +83,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
         self.assertEqual(cfms.function.getParameterValue('ion1.pk0.FWHM'), 1.5)
         self.assertAlmostEqual(cfms.function.getParameterValue('ion1.pk1.PeakCentre'), 1749.981919, 6)
 
-    def test_peak_values_multiple_ions_and_spectra(self):
+    def test_peak_values_multi_ions_and_spectra(self):
         cfms = CrystalFieldMultiSite(Ions=('Pm', 'Ce'), Symmetries=('D2', 'C3v'), Temperatures=[20, 52],
                                      FWHMs=[1.0, 1.0], parameters={'ion0.B40': -0.02, 'ion1.B42': -0.12})
         self.assertEqual(int(cfms.function.getParameterValue('ion0.sp0.pk0.Amplitude')), 308)
@@ -93,7 +93,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
         self.assertEqual(cfms.function.getParameterValue('ion1.sp1.pk0.FWHM'), 1.0)
         self.assertAlmostEqual(cfms.function.getParameterValue('ion1.sp0.pk1.PeakCentre'), 8.519155, 6)
 
-    def test_peak_values_multiple_gaussian(self):
+    def test_peak_values_multi_gaussian(self):
         cfms = CrystalFieldMultiSite(Ions=('Pm', 'Dy'), Symmetries=('D2', 'C3v'), Temperatures=[20, 52],
                                      FWHMs=[1.0, 1.5], parameters={'ion0.B40': -0.02, 'ion1.B42': -0.12},
                                      PeakShape='Gaussian')
@@ -111,7 +111,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
         r = [0.0, 1.45, 2.4, 3.0, 3.85]
         x, y = cfms.getSpectrum(r)
         y = y / c_mbsr
-        expected_y = [13.94816, 0.016566, 0.006051, 0.003873, 0.002352]
+        expected_y = [13.950363, 0.02298, 0.031946, 0.189161, 0.392888]
         np.testing.assert_equal(x, r)
         np.testing.assert_almost_equal(y, expected_y, 6)
 
@@ -126,24 +126,24 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
         ws = CreateWorkspace(x, y, e)
         x, y = cfms.getSpectrum(0, ws)
         y = y / c_mbsr
-        self.assertAlmostEqual(y[0], 12.471600, 6)
-        self.assertAlmostEqual(y[1], 4.296852, 6)
-        self.assertAlmostEqual(y[2], 1.448504, 6)
-        self.assertAlmostEqual(y[3], 0.688184, 6)
-        self.assertAlmostEqual(y[4], 0.396680, 6)
-        self.assertAlmostEqual(y[15], 0.029067, 6)
-        self.assertAlmostEqual(y[16], 0.025555, 6)
+        self.assertAlmostEqual(y[0], 12.474955, 6)
+        self.assertAlmostEqual(y[1], 4.300416, 6)
+        self.assertAlmostEqual(y[2], 1.452309, 6)
+        self.assertAlmostEqual(y[3], 0.692266, 6)
+        self.assertAlmostEqual(y[4], 0.401079, 6)
+        self.assertAlmostEqual(y[15], 0.050130, 6)
+        self.assertAlmostEqual(y[16], 0.054428, 6)
         x, y = cfms.getSpectrum(ws)
         y = y / c_mbsr
-        self.assertAlmostEqual(y[0], 12.471600, 6)
-        self.assertAlmostEqual(y[1], 4.296852, 6)
+        self.assertAlmostEqual(y[0], 12.474955, 6)
+        self.assertAlmostEqual(y[1], 4.300416, 6)
         ws = CreateWorkspace(x, y, e, 2)
         x, y = cfms.getSpectrum(ws, 1)
         y = y / c_mbsr
-        self.assertAlmostEqual(y[0], 0.029067, 6)
-        self.assertAlmostEqual(y[1], 0.025555, 6)
+        self.assertAlmostEqual(y[0], 0.050130, 6)
+        self.assertAlmostEqual(y[1], 0.054428, 6)
 
-    def test_get_spectrum_from_list_multiple_spectra(self):
+    def test_get_spectrum_from_list_multi_spectra(self):
         cfms = CrystalFieldMultiSite(Ions=['Ce'], Symmetries=['C2v'], Temperatures=[4.0, 50.0], FWHMs=[0.1, 0.2],
                                      B20=0.035, B40=-0.012, B43=-0.027, B60=-0.00012, B63=0.0025, B66=0.0068)
         r = [0.0, 1.45, 2.4, 3.0, 3.85]
@@ -159,7 +159,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
         np.testing.assert_equal(x, r)
         np.testing.assert_almost_equal(y, expected_y, 6)
 
-    def test_get_spectrum_ws_multiple_spectra(self):
+    def test_get_spectrum_ws_multi_spectra(self):
         from mantid.simpleapi import CreateWorkspace
         cfms = CrystalFieldMultiSite(['Ce'], ['C2v'], B20=0.035, B40=-0.012, B43=-0.027, B60=-0.00012, B63=0.0025, B66=0.0068,
                           Temperatures=[4.0, 50.0], FWHMs=[0.1, 0.2])
@@ -194,7 +194,26 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
         self.assertAlmostEqual(y[0], 0.050129858433581413, 6)
         self.assertAlmostEqual(y[1], 0.054427788297191478, 6)
 
-    def test_multi_ion_single_spectrum_fit(self):
+    def test_get_spectrum_list_multi_ion_and_spectra(self):
+        params = {'ion0.B20': 0.37737, 'ion0.B22': 3.9770, 'ion0.B40': -0.031787, 'ion0.B42': -0.11611,
+                  'ion0.B44': -0.12544, 'ion1.B20': 0.37737, 'ion1.B22': 3.9770, 'ion1.B40': -0.031787,
+                  'ion1.B42': -0.11611, 'ion1.B44': -0.12544}
+        cfms = CrystalFieldMultiSite(Ions=['Ce', 'Pr'], Symmetries=['C2v', 'C2v'], Temperatures=[44.0, 50.0],
+                                     FWHMs=[1.1, 1.2], parameters=params)
+        r = [0.0, 1.45, 2.4, 3.0, 3.85]
+        x, y = cfms.getSpectrum(0, r)
+        y = y / c_mbsr
+        expected_y = [3.904037, 0.744519, 0.274897, 0.175713, 0.106540]
+        np.testing.assert_equal(x, r)
+        np.testing.assert_almost_equal(y, expected_y, 6)
+
+        x, y = cfms.getSpectrum(1, r)
+        y = y / c_mbsr
+        expected_y = [3.704726, 0.785600, 0.296255, 0.190176, 0.115650]
+        np.testing.assert_equal(x, r)
+        np.testing.assert_almost_equal(y, expected_y, 6)
+
+    def test_fit_multi_ion_single_spectrum(self):
 
         from CrystalField.fitting import makeWorkspace
         from CrystalField import CrystalField, CrystalFieldFit
@@ -209,15 +228,67 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
         ws = makeWorkspace(x, y)
 
         params = {'ion0.B20': 0.37737, 'ion0.B22': 3.9770, 'ion0.B40': -0.031787, 'ion0.B42': -0.11611,
-                  'ion0.B44': -0.12544, 'ion1.B20': 0.37737, 'ion1.B22': 3.9770, 'ion1.B40': -0.031787, 'ion1.B42': -0.11611,
-                  'ion1.B44': -0.12544}
+                  'ion0.B44': -0.12544, 'ion1.B20': 0.37737, 'ion1.B22': 3.9770, 'ion1.B40': -0.031787,
+                  'ion1.B42': -0.11611, 'ion1.B44': -0.12544}
         cf = CrystalFieldMultiSite(Ions=['Ce', 'Pr'], Symmetries=['C2v', 'C2v'], Temperatures=[44.0], FWHMs=[1.1],
-                                   ToleranceIntensity=6.0, ToleranceEnergy=1.0, parameters=params)
+                                   ToleranceIntensity=6.0, ToleranceEnergy=1.0, FixAllPeaks=True, parameters=params)
+
+        cf.fix('ion0.BmolX', 'ion0.BmolY', 'ion0.BmolZ', 'ion0.BextX', 'ion0.BextY', 'ion0.BextZ', 'ion0.B40', 
+               'ion0.B42', 'ion0.B44', 'ion0.B60', 'ion0.B62', 'ion0.B64', 'ion0.B66', 'ion0.IntensityScaling',
+               'ion1.BmolX', 'ion1.BmolY', 'ion1.BmolZ', 'ion1.BextX', 'ion1.BextY', 'ion1.BextZ', 'ion1.B40',
+               'ion1.B42', 'ion1.B44', 'ion1.B60', 'ion1.B62', 'ion1.B64', 'ion1.B66', 'ion1.IntensityScaling')
 
         chi2 = CalculateChiSquared(cf.makeSpectrumFunction(), InputWorkspace=ws)[1]
 
         fit = CrystalFieldFit(Model=cf, InputWorkspace=ws, MaxIterations=10)
         fit.fit()
 
+        f = cf.function
+        for i in range(f.nParams()):
+            if not f.isFixed(i):
+                print i, f.parameterName(i), f.getParameterValue(i)
+
+        self.assertFalse(True)
+
+        self.assertTrue(cf.chi2 > 0.0)
+        self.assertTrue(cf.chi2 < chi2)
+
+    def test_fit_multi_ion_and_spectra(self):
+        from CrystalField.fitting import makeWorkspace
+        from CrystalField import CrystalField, CrystalFieldFit
+        from mantid.simpleapi import CalculateChiSquared
+
+        params = {'B20': 0.37737, 'B22': 3.9770, 'B40': -0.031787, 'B42': -0.11611, 'B44': -0.12544,
+                  'Temperature': [44.0, 50.0], 'FWHM': [1.1, 0.9]}
+        cf1 = CrystalField('Ce', 'C2v', **params)
+        cf2 = CrystalField('Pr', 'C2v', **params)
+        cf = cf1 + cf2
+        ws1 = makeWorkspace(*cf.getSpectrum(0))
+        ws2 = makeWorkspace(*cf.getSpectrum(1))
+
+        params = {'ion0.B20': 0.37737, 'ion0.B22': 3.9770, 'ion0.B40': -0.031787, 'ion0.B42': -0.11611,
+                  'ion0.B44': -0.12544, 'ion1.B20': 0.37737, 'ion1.B22': 3.9770, 'ion1.B40': -0.031787,
+                  'ion1.B42': -0.11611, 'ion1.B44': -0.12544}
+        cf = CrystalFieldMultiSite(Ions=['Ce', 'Pr'], Symmetries=['C2v', 'C2v'], Temperatures=[44.0, 50.0],
+                                    FWHMs=[1.0, 1.0], ToleranceIntensity=6.0, ToleranceEnergy=1.0,  FixAllPeaks=True,
+                                   parameters=params)
+
+        cf.fix('ion0.BmolX', 'ion0.BmolY', 'ion0.BmolZ', 'ion0.BextX', 'ion0.BextY', 'ion0.BextZ', 'ion0.B40',
+               'ion0.B42', 'ion0.B44', 'ion0.B60', 'ion0.B62', 'ion0.B64', 'ion0.B66', 'ion0.IntensityScaling',
+               'ion1.BmolX', 'ion1.BmolY', 'ion1.BmolZ', 'ion1.BextX', 'ion1.BextY', 'ion1.BextZ', 'ion1.B40',
+               'ion1.B42', 'ion1.B44', 'ion1.B60', 'ion1.B62', 'ion1.B64', 'ion1.B66', 'ion1.IntensityScaling',
+               'sp0.IntensityScaling', 'sp1.IntensityScaling')
+
+        chi2 = CalculateChiSquared(str(cf.function), InputWorkspace=ws1, InputWorkspace_1=ws2)[1]
+
+        fit = CrystalFieldFit(Model=cf, InputWorkspace=[ws1, ws2], MaxIterations=10)
+        fit.fit()
+
+        f = cf.function
+        for i in range(f.nParams()):
+            if not f.isFixed(i):
+                print i, f.parameterName(i), f.getParameterValue(i)
+
         self.assertTrue(cf.chi2 > 0.0)
-        self.assertTrue(cf.chi2 < chi2)
\ No newline at end of file
+        self.assertTrue(cf.chi2 < chi2)
+        self.assertFalse(True)
\ No newline at end of file
-- 
GitLab