diff --git a/Framework/CurveFitting/inc/MantidCurveFitting/Functions/CrystalFieldControl.h b/Framework/CurveFitting/inc/MantidCurveFitting/Functions/CrystalFieldControl.h
index c13ca3b7e4e49806613f992378217c508bb0faf9..2f75b05900291d8235f0efb86188464fa576bec8 100644
--- a/Framework/CurveFitting/inc/MantidCurveFitting/Functions/CrystalFieldControl.h
+++ b/Framework/CurveFitting/inc/MantidCurveFitting/Functions/CrystalFieldControl.h
@@ -88,10 +88,6 @@ private:
   std::vector<double> m_temperatures;
   /// Cache the default peak FWHMs
   std::vector<double> m_FWHMs;
-  /// Cache number of fitted peaks
-  // mutable std::vector<size_t> m_nPeaks;
-  /// Cache the list of "spectra" corresponding to physical properties
-  std::vector<int> m_physprops;
   /// Caches of the width functions
   std::vector<std::vector<double>> m_fwhmX;
   std::vector<std::vector<double>> m_fwhmY;
diff --git a/Framework/CurveFitting/inc/MantidCurveFitting/Functions/CrystalFieldFunction.h b/Framework/CurveFitting/inc/MantidCurveFitting/Functions/CrystalFieldFunction.h
index 29080f77220e60f7118d96e4bf3d808d93ece112..4168512286dc9117c90f8c45d4693e0682b36575 100644
--- a/Framework/CurveFitting/inc/MantidCurveFitting/Functions/CrystalFieldFunction.h
+++ b/Framework/CurveFitting/inc/MantidCurveFitting/Functions/CrystalFieldFunction.h
@@ -241,6 +241,7 @@ private:
       m_mapPrefixes2PhysProps;
   /// Temporary cache for parameter values during source function resetting.
   mutable std::vector<double> m_parameterResetCache;
+  mutable std::vector<bool> m_fixResetCache;
 };
 
 } // namespace Functions
diff --git a/Framework/CurveFitting/src/Functions/CrystalFieldControl.cpp b/Framework/CurveFitting/src/Functions/CrystalFieldControl.cpp
index b017bee64d023f445d6d516c859df8417a33d253..ff604962cb3b2be6c93d07fbc6a2c4517561df5a 100644
--- a/Framework/CurveFitting/src/Functions/CrystalFieldControl.cpp
+++ b/Framework/CurveFitting/src/Functions/CrystalFieldControl.cpp
@@ -55,10 +55,31 @@ void CrystalFieldControl::setAttribute(const std::string &name,
       m_temperatures = attr.asVector();
       buildControls();
     } else if (name == "FWHMs") {
+      const size_t nSpec = m_temperatures.size();
       m_FWHMs = attr.asVector();
-      if (m_FWHMs.size() == 1 && m_FWHMs.size() != m_temperatures.size()) {
-        m_FWHMs.assign(m_temperatures.size(), m_FWHMs.front());
+      if (m_FWHMs.size() == 1 && m_FWHMs.size() != nSpec) {
+        m_FWHMs.assign(nSpec, m_FWHMs.front());
+      }
+      if (!m_FWHMs.empty()) {
+        m_fwhmX.resize(nSpec);
+        m_fwhmY.resize(nSpec);
+        for (size_t i = 0; i < nSpec; ++i) {
+          m_fwhmX[i].clear();
+          m_fwhmY[i].clear();
+          if (nSpec > 1) {
+            auto &control = *getFunction(i);
+            control.setAttribute("FWHMX", Attribute(std::vector<double>()));
+            control.setAttribute("FWHMY", Attribute(std::vector<double>()));
+          } else {
+            setAttribute("FWHMX", Attribute(std::vector<double>()));
+            setAttribute("FWHMY", Attribute(std::vector<double>()));
+          }
+        }
       }
+    } else if ((name.compare(0, 5, "FWHMX") == 0 ||
+                name.compare(0, 5, "FWHMY") == 0) &&
+               !attr.asVector().empty()) {
+      m_FWHMs.clear();
     }
     API::IFunction::setAttribute(name, attr);
   }
diff --git a/Framework/CurveFitting/src/Functions/CrystalFieldFunction.cpp b/Framework/CurveFitting/src/Functions/CrystalFieldFunction.cpp
index df242ad42379f308cfd3986aa84597715baa8991..99c8063ad8e508a98fbbbb2e88b7ef10da61b6d7 100644
--- a/Framework/CurveFitting/src/Functions/CrystalFieldFunction.cpp
+++ b/Framework/CurveFitting/src/Functions/CrystalFieldFunction.cpp
@@ -492,6 +492,10 @@ void CrystalFieldFunction::setAttribute(const std::string &attName,
     m_source.reset();
   }
   attRef.first->setAttribute(attRef.second, attr);
+  if (attName.find("FWHM") != std::string::npos ||
+      attName.find("Background") != std::string::npos) {
+    m_dirtyTarget = true;
+  }
 }
 
 /// Check if attribute attName exists
@@ -632,9 +636,12 @@ void CrystalFieldFunction::buildSourceFunction() const {
       m_parameterResetCache.size() == m_source->nParams()) {
     for (size_t i = 0; i < m_parameterResetCache.size(); ++i) {
       m_source->setParameter(i, m_parameterResetCache[i]);
+      if (m_fixResetCache[i])
+        m_source->fix(i);
     }
   }
   m_parameterResetCache.clear();
+  m_fixResetCache.clear();
 }
 
 /// Update spectrum function if necessary.
@@ -741,9 +748,9 @@ void CrystalFieldFunction::buildSingleSiteMultiSpectrum() const {
   for (size_t i = 0; i < nSpec; ++i) {
     auto intensityScaling =
         m_control.getFunction(i)->getParameter("IntensityScaling");
-    fun->addFunction(buildSpectrum(nre, energies, waveFunctions,
-                                   temperatures[i], FWHMs[i], i, addBackground,
-                                   intensityScaling));
+    fun->addFunction(buildSpectrum(
+        nre, energies, waveFunctions, temperatures[i],
+        FWHMs.size() > i ? FWHMs[i] : 0., i, addBackground, intensityScaling));
     fun->setDomainIndex(i, i);
   }
   auto &physProps = m_control.physProps();
@@ -843,9 +850,10 @@ void CrystalFieldFunction::buildMultiSiteMultiSpectrum() const {
     for (size_t i = 0; i < nSpec; ++i) {
       auto spectrumIntensityScaling =
           m_control.getFunction(i)->getParameter("IntensityScaling");
-      spectra[i]->addFunction(buildSpectrum(
-          nre, energies, waveFunctions, temperatures[i], FWHMs[i], i,
-          addBackground, ionIntensityScaling * spectrumIntensityScaling));
+      spectra[i]->addFunction(
+          buildSpectrum(nre, energies, waveFunctions, temperatures[i],
+                        FWHMs.size() > i ? FWHMs[i] : 0., i, addBackground,
+                        ionIntensityScaling * spectrumIntensityScaling));
     }
 
     size_t i = 0;
@@ -934,7 +942,7 @@ API::IFunction_sptr CrystalFieldFunction::buildSpectrum(
   }
 
   auto xVec = m_control.getFunction(iSpec)->getAttribute("FWHMX").asVector();
-  auto yVec = m_control.getFunction(iSpec)->getAttribute("FWHMX").asVector();
+  auto yVec = m_control.getFunction(iSpec)->getAttribute("FWHMY").asVector();
   CrystalFieldUtils::buildSpectrumFunction(*spectrum, peakShape, values, xVec,
                                            yVec, fwhmVariation, fwhm,
                                            nRequiredPeaks, fixAllPeaks);
@@ -1052,7 +1060,7 @@ void CrystalFieldFunction::updateSingleSiteSingleSpectrum() const {
   auto peakShape = m_control.getAttribute("PeakShape").asString();
   bool fixAllPeaks = m_control.getAttribute("FixAllPeaks").asBool();
   auto xVec = m_control.getAttribute("FWHMX").asVector();
-  auto yVec = m_control.getAttribute("FWHMX").asVector();
+  auto yVec = m_control.getAttribute("FWHMY").asVector();
   auto &FWHMs = m_control.FWHMs();
   auto defaultFWHM = FWHMs.empty() ? 0.0 : FWHMs[0];
   size_t indexShift = hasBackground() ? 1 : 0;
@@ -1085,7 +1093,8 @@ void CrystalFieldFunction::updateSingleSiteMultiSpectrum() const {
   auto &FWHMs = m_control.FWHMs();
   for (size_t iSpec = 0; iSpec < temperatures.size(); ++iSpec) {
     updateSpectrum(*fun.getFunction(iSpec), nre, energies, waveFunctions,
-                   temperatures[iSpec], FWHMs[iSpec], iSpec, iFirst);
+                   temperatures[iSpec],
+                   FWHMs.size() > iSpec ? FWHMs[iSpec] : 0., iSpec, iFirst);
   }
 
   for (auto &prop : m_mapPrefixes2PhysProps) {
@@ -1099,7 +1108,7 @@ void CrystalFieldFunction::updateMultiSiteSingleSpectrum() const {
   auto peakShape = getAttribute("PeakShape").asString();
   bool fixAllPeaks = getAttribute("FixAllPeaks").asBool();
   auto xVec = m_control.getAttribute("FWHMX").asVector();
-  auto yVec = m_control.getAttribute("FWHMX").asVector();
+  auto yVec = m_control.getAttribute("FWHMY").asVector();
   auto &FWHMs = m_control.FWHMs();
   auto defaultFWHM = FWHMs.empty() ? 0.0 : FWHMs[0];
 
@@ -1142,7 +1151,8 @@ void CrystalFieldFunction::updateMultiSiteMultiSpectrum() const {
       auto &ionSpectrum =
           dynamic_cast<CompositeFunction &>(*spectrum.getFunction(ionIndex));
       updateSpectrum(ionSpectrum, nre, energies, waveFunctions,
-                     temperatures[iSpec], FWHMs[iSpec], iSpec, iFirst);
+                     temperatures[iSpec],
+                     FWHMs.size() > iSpec ? FWHMs[iSpec] : 0., iSpec, iFirst);
     }
 
     std::string prefix("ion");
@@ -1174,7 +1184,7 @@ void CrystalFieldFunction::updateSpectrum(
   const auto peakShape = getAttribute("PeakShape").asString();
   const bool fixAllPeaks = getAttribute("FixAllPeaks").asBool();
   auto xVec = m_control.getFunction(iSpec)->getAttribute("FWHMX").asVector();
-  auto yVec = m_control.getFunction(iSpec)->getAttribute("FWHMX").asVector();
+  auto yVec = m_control.getFunction(iSpec)->getAttribute("FWHMY").asVector();
   auto intensityScaling =
       m_control.getFunction(iSpec)->getParameter("IntensityScaling");
   FunctionValues values;
@@ -1390,8 +1400,10 @@ void CrystalFieldFunction::cacheSourceParameters() const {
   }
   auto np = m_source->nParams();
   m_parameterResetCache.resize(np);
+  m_fixResetCache.resize(np);
   for (size_t i = 0; i < np; ++i) {
     m_parameterResetCache[i] = m_source->getParameter(i);
+    m_fixResetCache[i] = m_source->isFixed(i);
   }
 }
 
diff --git a/Framework/CurveFitting/src/Functions/CrystalFieldMultiSpectrum.cpp b/Framework/CurveFitting/src/Functions/CrystalFieldMultiSpectrum.cpp
index 246fec09c7f9e1398a8f8b857c20006f2e891e37..f1af30eea7407b205972ccd4e73adc63fcabba0a 100644
--- a/Framework/CurveFitting/src/Functions/CrystalFieldMultiSpectrum.cpp
+++ b/Framework/CurveFitting/src/Functions/CrystalFieldMultiSpectrum.cpp
@@ -15,6 +15,7 @@
 #include "MantidAPI/ParameterTie.h"
 
 #include "MantidKernel/Exception.h"
+#include <boost/regex.hpp>
 #include <iostream>
 
 namespace Mantid {
@@ -29,6 +30,10 @@ DECLARE_FUNCTION(CrystalFieldMultiSpectrum)
 
 namespace {
 
+// Regex for the FWHMX# type strings (single-site mode)
+const boost::regex FWHMX_ATTR_REGEX("FWHMX([0-9]+)");
+const boost::regex FWHMY_ATTR_REGEX("FWHMY([0-9]+)");
+
 /// Define the source function for CrystalFieldMultiSpectrum.
 /// Its function() method is not needed.
 class Peaks : public CrystalFieldPeaksBase, public API::IFunctionGeneral {
@@ -51,11 +56,15 @@ public:
   std::vector<size_t> m_IntensityScalingIdx;
   std::vector<size_t> m_PPLambdaIdxChild;
   std::vector<size_t> m_PPLambdaIdxSelf;
+  std::vector<size_t> m_PPChi0IdxChild;
+  std::vector<size_t> m_PPChi0IdxSelf;
   /// Declare the intensity scaling parameters: one per spectrum.
   void declareIntensityScaling(size_t nSpec) {
     m_IntensityScalingIdx.clear();
     m_PPLambdaIdxChild.resize(nSpec, -1);
     m_PPLambdaIdxSelf.resize(nSpec, -1);
+    m_PPChi0IdxChild.resize(nSpec, -1);
+    m_PPChi0IdxSelf.resize(nSpec, -1);
     for (size_t i = 0; i < nSpec; ++i) {
       auto si = std::to_string(i);
       try { // If parameter has already been declared, don't declare it.
@@ -71,6 +80,8 @@ public:
     if (m_PPLambdaIdxSelf.size() <= iSpec) {
       m_PPLambdaIdxSelf.resize(iSpec + 1, -1);
       m_PPLambdaIdxChild.resize(iSpec + 1, -1);
+      m_PPChi0IdxSelf.resize(iSpec + 1, -1);
+      m_PPChi0IdxChild.resize(iSpec + 1, -1);
     }
     auto si = std::to_string(iSpec);
     try { // If parameter has already been declared, don't declare it.
@@ -78,7 +89,13 @@ public:
                        "Effective exchange coupling of dataset " + si);
     } catch (std::invalid_argument &) {
     }
+    try { // If parameter has already been declared, don't declare it.
+      declareParameter("Chi0" + si, 0.0,
+                       "Effective exchange coupling of dataset " + si);
+    } catch (std::invalid_argument &) {
+    }
     m_PPLambdaIdxSelf[iSpec] = parameterIndex("Lambda" + si);
+    m_PPChi0IdxSelf[iSpec] = parameterIndex("Chi0" + si);
   }
 };
 }
@@ -123,6 +140,7 @@ CrystalFieldMultiSpectrum::createEquivalentFunctions() const {
 /// Perform custom actions on setting certain attributes.
 void CrystalFieldMultiSpectrum::setAttribute(const std::string &name,
                                              const Attribute &attr) {
+  boost::smatch match;
   if (name == "Temperatures") {
     // Define (declare) the parameters for intensity scaling.
     const auto nSpec = attr.asVector().size();
@@ -130,13 +148,23 @@ void CrystalFieldMultiSpectrum::setAttribute(const std::string &name,
     m_nOwnParams = m_source->nParams();
     m_fwhmX.resize(nSpec);
     m_fwhmY.resize(nSpec);
+    std::vector<double> new_fwhm = getAttribute("FWHMs").asVector();
+    const auto nWidths = new_fwhm.size();
+    if (nWidths != nSpec) {
+      new_fwhm.resize(nSpec);
+      if (nWidths > nSpec) {
+        for (size_t iSpec = nWidths; iSpec < nSpec; ++iSpec) {
+          new_fwhm[iSpec] = new_fwhm[0];
+        }
+      }
+    }
+    FunctionGenerator::setAttribute("FWHMs", Attribute(new_fwhm));
     for (size_t iSpec = 0; iSpec < nSpec; ++iSpec) {
       const auto suffix = std::to_string(iSpec);
       declareAttribute("FWHMX" + suffix, Attribute(m_fwhmX[iSpec]));
       declareAttribute("FWHMY" + suffix, Attribute(m_fwhmY[iSpec]));
     }
-  }
-  if (name == "PhysicalProperties") {
+  } else if (name == "PhysicalProperties") {
     const auto physpropId = attr.asVector();
     const auto nSpec = physpropId.size();
     auto &source = dynamic_cast<Peaks &>(*m_source);
@@ -162,6 +190,12 @@ void CrystalFieldMultiSpectrum::setAttribute(const std::string &name,
         m_nOwnParams = m_source->nParams();
       }
     }
+  } else if (boost::regex_match(name, match, FWHMX_ATTR_REGEX)) {
+    auto iSpec = std::stoul(match[1]);
+    m_fwhmX[iSpec].clear();
+  } else if (boost::regex_match(name, match, FWHMY_ATTR_REGEX)) {
+    auto iSpec = std::stoul(match[1]);
+    m_fwhmY[iSpec].clear();
   }
   FunctionGenerator::setAttribute(name, attr);
 }
@@ -332,6 +366,8 @@ API::IFunction_sptr CrystalFieldMultiSpectrum::buildPhysprop(
     spectrum.setAttribute("powder", getAttribute("powder" + suffix));
     dynamic_cast<Peaks &>(*m_source).m_PPLambdaIdxChild[iSpec] =
         spectrum.parameterIndex("Lambda");
+    dynamic_cast<Peaks &>(*m_source).m_PPChi0IdxChild[iSpec] =
+        spectrum.parameterIndex("Chi0");
     return retval;
   }
   case Magnetisation: {
@@ -408,6 +444,8 @@ void CrystalFieldMultiSpectrum::updateSpectrum(
     auto &source = dynamic_cast<Peaks &>(*m_source);
     suscept.setParameter(source.m_PPLambdaIdxChild[iSpec],
                          getParameter(source.m_PPLambdaIdxSelf[iSpec]));
+    suscept.setParameter(source.m_PPChi0IdxChild[iSpec],
+                         getParameter(source.m_PPChi0IdxSelf[iSpec]));
     break;
   }
   case Magnetisation: {
diff --git a/Framework/CurveFitting/src/Functions/CrystalFieldSusceptibility.cpp b/Framework/CurveFitting/src/Functions/CrystalFieldSusceptibility.cpp
index 123c6ef59a6ce8622eee2bfe981d38957fa62ac5..26d83385c0c99c0183cbd8861b513543c087d8a8 100644
--- a/Framework/CurveFitting/src/Functions/CrystalFieldSusceptibility.cpp
+++ b/Framework/CurveFitting/src/Functions/CrystalFieldSusceptibility.cpp
@@ -139,6 +139,19 @@ void CrystalFieldSusceptibilityBase::function1D(double *out,
   } else {
     calculate(out, xValues, nData, m_en, m_wf, m_nre, H, convfact);
   }
+  const double lambda = getParameter("Lambda");
+  const double EPS = 1.e-6;
+  if (fabs(lambda) > EPS) {
+    for (size_t i = 0; i < nData; i++) {
+      out[i] /= (1. - lambda * out[i]); // chi = chi_cf/(1 - lambda.chi_cf)
+    }
+  }
+  const double chi0 = getParameter("Chi0");
+  if (fabs(lambda) > 1.e-6) {
+    for (size_t i = 0; i < nData; i++) {
+      out[i] += chi0;
+    }
+  }
   if (getAttribute("inverse").asBool()) {
     for (size_t i = 0; i < nData; i++) {
       out[i] = 1. / out[i];
@@ -150,12 +163,6 @@ void CrystalFieldSusceptibilityBase::function1D(double *out,
       out[i] *= fact;
     }
   }
-  const double lambda = getParameter("Lambda");
-  if (fabs(lambda) > 1.e-6) {
-    for (size_t i = 0; i < nData; i++) {
-      out[i] /= (1. - lambda * out[i]); // chi = chi0/(1 - lambda.chi0)
-    }
-  }
 }
 
 DECLARE_FUNCTION(CrystalFieldSusceptibility)
@@ -164,6 +171,7 @@ CrystalFieldSusceptibility::CrystalFieldSusceptibility()
     : CrystalFieldPeaksBase(), CrystalFieldSusceptibilityBase(),
       m_setDirect(false) {
   declareParameter("Lambda", 0.0, "Effective exchange interaction");
+  declareParameter("Chi0", 0.0, "Background or remnant susceptibility");
 }
 
 // Sets the eigenvectors / values directly
@@ -186,6 +194,7 @@ void CrystalFieldSusceptibility::function1D(double *out, const double *xValues,
 
 CrystalFieldSusceptibilityCalculation::CrystalFieldSusceptibilityCalculation() {
   declareParameter("Lambda", 0.0, "Effective exchange interaction");
+  declareParameter("Chi0", 0.0, "Background or remnant susceptibility");
 }
 
 // Sets the eigenvectors / values directly
diff --git a/Framework/PythonInterface/mantid/api/src/Exports/IFunction.cpp b/Framework/PythonInterface/mantid/api/src/Exports/IFunction.cpp
index 7e132662437cd9b759b352623aa24684a488dc16..0406931e6735830975e7cd13522739a56909c6a0 100644
--- a/Framework/PythonInterface/mantid/api/src/Exports/IFunction.cpp
+++ b/Framework/PythonInterface/mantid/api/src/Exports/IFunction.cpp
@@ -209,6 +209,9 @@ void export_IFunction() {
            (void (IFunction::*)(const std::string &)) & IFunction::removeTie,
            (arg("self"), arg("name")), "Remove the tie of the named parameter")
 
+      .def("getTies", &IFunction::writeTies, arg("self"),
+           "Returns the list of current ties as a string")
+
       .def("addConstraints", &IFunction::addConstraints,
            addConstraints_Overloads(
                (arg("self"), arg("constraints"), arg("isDefault")),
@@ -218,6 +221,9 @@ void export_IFunction() {
            (arg("self"), arg("name")),
            "Remove the constraint on the named parameter")
 
+      .def("getConstraints", &IFunction::writeConstraints, arg("self"),
+           "Returns the list of current constraints as a string")
+
       .def("getNumberDomains", &IFunction::getNumberDomains, (arg("self")),
            "Get number of domains of a multi-domain function")
 
diff --git a/Testing/Data/SystemTest/Sm2O3.cif.md5 b/Testing/Data/SystemTest/Sm2O3.cif.md5
new file mode 100644
index 0000000000000000000000000000000000000000..4ca349a92ca0f0a33fefdf7e4e9a73c8310bbcf6
--- /dev/null
+++ b/Testing/Data/SystemTest/Sm2O3.cif.md5
@@ -0,0 +1 @@
+1d127c4d48f67c2260e06e1844e87725
diff --git a/Testing/SystemTests/tests/analysis/CrystalFieldPythonInterface.py b/Testing/SystemTests/tests/analysis/CrystalFieldPythonInterface.py
new file mode 100644
index 0000000000000000000000000000000000000000..7dbf920e6b907049e3f99fbc7bcd66e371abd162
--- /dev/null
+++ b/Testing/SystemTests/tests/analysis/CrystalFieldPythonInterface.py
@@ -0,0 +1,391 @@
+#pylint: disable=no-init
+from stresstesting import MantidStressTest
+from mantid.simpleapi import CreateWorkspace, LinearBackground, FlatBackground, Gaussian, CalculateChiSquared, mtd
+from CrystalField.fitting import makeWorkspace
+from PyChop import PyChop2
+import numpy as np
+
+
+class CrystalFieldPythonInterface(MantidStressTest):
+    """ Runs all the commands in the crystal field python interface documentation file
+        This test only checks that the syntax of all the commands are still valid.
+        Unit tests for the individual functions / fitting procedure check for correctness.
+    """
+
+    def my_create_ws(self, outwsname, x, y):
+        jitter = (np.random.rand(np.shape(y)[0])-0.5)*np.max(y)/100
+        CreateWorkspace(x, y + jitter, y*0+np.max(y)/100, Distribution=True, OutputWorkspace=outwsname)
+        return mtd[outwsname]
+
+    def my_func(self, en):
+        return (25-en)**(1.5) / 200 + 0.1
+
+    def runTest(self):
+        from CrystalField import CrystalField, CrystalFieldFit, CrystalFieldMultiSite, Background, Function, ResolutionModel
+
+        cf = CrystalField('Ce', 'C2v')
+        cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770)
+        cf['B40'] = -0.031
+        b = cf['B40']
+
+        # Calculate and return the Hamiltonian matrix as a 2D numpy array.
+        h = cf.getHamiltonian()
+        print(h)
+        # Calculate and return the eigenvalues of the Hamiltonian as a 1D numpy array.
+        e = cf.getEigenvalues()
+        print(e)
+        # Calculate and return the eigenvectors of the Hamiltonian as a 2D numpy array.
+        w = cf.getEigenvectors()
+        print(w)
+        # Using the keyword argument
+        cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, Temperature=44)
+        # Using the property
+        cf.Temperature = 44
+
+        print(cf.getPeakList())
+        #[[  0.00000000e+00   2.44006198e+01   4.24977124e+01   1.80970926e+01 -2.44006198e+01]
+        # [  2.16711565e+02   8.83098530e+01   5.04430056e+00   1.71153708e-01  1.41609425e-01]]
+        cf.ToleranceIntensity = 1
+        print(cf.getPeakList())
+        #[[   0.           24.40061976   42.49771237]
+        # [ 216.71156467   88.30985303    5.04430056]]
+        cf.PeakShape = 'Gaussian'
+        cf.FWHM = 0.9
+        sp = cf.getSpectrum()
+        print(cf.function)
+        CrystalField_Ce = CreateWorkspace(*sp)
+        print(CrystalField_Ce)
+
+        # If the peak shape is Gaussian
+        cf.peaks.param[1]['Sigma'] = 2.0
+        cf.peaks.param[2]['Sigma'] = 0.01
+
+        # If the peak shape is Lorentzian
+        cf.PeakShape = 'Lorentzian'
+        cf.peaks.param[1]['FWHM'] = 2.0
+        cf.peaks.param[2]['FWHM'] = 0.01
+
+        cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, B40=-0.031787, B42=-0.11611, B44=-0.12544,
+                          Temperature=44.0, FWHM=1.1)
+        cf.background = Background(peak=Function('Gaussian', Height=10, Sigma=1),
+                                   background=Function('LinearBackground', A0=1.0, A1=0.01))
+        h = cf.background.peak.param['Height']
+        a1 = cf.background.background.param['A1']
+        print(h)
+        print(a1)
+
+        cf.ties(B20=1.0, B40='B20/2')
+        cf.constraints('1 < B22 <= 2', 'B22 < 4')
+
+        print(cf.function[1].getTies())
+        print(cf.function[1].getConstraints())
+
+        cf.background.peak.ties(Height=10.1)
+        cf.background.peak.constraints('Sigma > 0')
+        cf.background.background.ties(A0=0.1)
+        cf.background.background.constraints('A1 > 0')
+
+        print(cf.function[0][0].getConstraints())
+        print(cf.function[0][1].getConstraints())
+        print(cf.function.getTies())
+        print(cf.function.getConstraints())
+
+        cf.peaks.ties({'f2.FWHM': '2*f1.FWHM', 'f3.FWHM': '2*f2.FWHM'})
+        cf.peaks.constraints('f0.FWHM < 2.2', 'f1.FWHM >= 0.1')
+
+        cf.PeakShape = 'Gaussian'
+        cf.peaks.tieAll('Sigma=0.1', 3)
+        cf.peaks.constrainAll('0 < Sigma < 0.1', 4)
+        cf.peaks.tieAll('Sigma=f0.Sigma', 1, 3)
+        cf.peaks.ties({'f1.Sigma': 'f0.Sigma', 'f2.Sigma': 'f0.Sigma', 'f3.Sigma': 'f0.Sigma'})
+
+        rm = ResolutionModel(([1, 2, 3, 100], [0.1, 0.3, 0.35, 2.1]))
+        cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, Temperature=44.0, ResolutionModel=rm)
+
+        rm = ResolutionModel(self.my_func, xstart=0.0, xend=24.0, accuracy=0.01)
+        cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, Temperature=44.0, ResolutionModel=rm)
+
+        marires = PyChop2('MARI')
+        marires.setChopper('S')
+        marires.setFrequency(250)
+        marires.setEi(30)
+        rm = ResolutionModel(marires.getResolution, xstart=0.0, xend=29.0, accuracy=0.01)
+        cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, Temperature=44.0, ResolutionModel=rm)
+
+        cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, Temperature=44.0, ResolutionModel=rm, FWHMVariation=0.1)
+
+        # ---------------------------
+
+        cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, B40=-0.031787, B42=-0.11611, B44=-0.12544,
+                          Temperature=[44.0, 50], FWHM=[1.1, 0.9])
+        cf.PeakShape = 'Lorentzian'
+        cf.peaks[0].param[0]['FWHM'] = 1.11
+        cf.peaks[1].param[1]['FWHM'] = 1.12
+        cf.background = Background(peak=Function('Gaussian', Height=10, Sigma=0.3),
+                                   background=Function('FlatBackground', A0=1.0))
+        cf.background[1].peak.param['Sigma'] = 0.8
+        cf.background[1].background.param['A0'] = 1.1
+
+        # The B parameters are common for all spectra - syntax doesn't change
+        cf.ties(B20=1.0, B40='B20/2')
+        cf.constraints('1 < B22 <= 2', 'B22 < 4')
+
+        # Backgrounds and peaks are different for different spectra - must be indexed
+        cf.background[0].peak.ties(Height=10.1)
+        cf.background[0].peak.constraints('Sigma > 0.1')
+        cf.background[1].peak.ties(Height=20.2)
+        cf.background[1].peak.constraints('Sigma > 0.2')
+        cf.peaks[1].tieAll('FWHM=2*f1.FWHM', 2, 5)
+        cf.peaks[0].constrainAll('FWHM < 2.2', 1, 4)
+
+        rm = ResolutionModel([self.my_func, marires.getResolution], 0, 100, accuracy = 0.01)
+        cf.ResolutionModel = rm
+
+        # Calculate second spectrum, use the generated x-values
+        sp = cf.getSpectrum(1)
+        # Calculate second spectrum, use the first spectrum of a workspace
+        sp = cf.getSpectrum(1, 'CrystalField_Ce')
+        # Calculate first spectrum, use the i-th spectrum of a workspace
+        i=0
+        sp = cf.getSpectrum(0, 'CrystalField_Ce', i)
+
+        print(cf.function)
+
+        cf.Temperature = [5, 50, 150]
+
+        print()
+        print(cf.function)
+
+        ws = 'CrystalField_Ce'
+        ws1 = 'CrystalField_Ce'
+        ws2 = 'CrystalField_Ce'
+        cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, B40=-0.031787, B42=-0.11611, B44=-0.12544, Temperature=5)
+
+        # In case of a single spectrum (ws is a workspace)
+        fit = CrystalFieldFit(Model=cf, InputWorkspace=ws)
+
+        # Or for multiple spectra
+        fit = CrystalFieldFit(Model=cf, InputWorkspace=[ws1, ws2])
+        cf.Temperature = [5, 50]
+        fit.fit()
+
+        params = {'B20': 0.377, 'B22': 3.9, 'B40': -0.03, 'B42': -0.116, 'B44': -0.125,
+                  'Temperature': [44.0, 50], 'FWHM': [1.1, 0.9]}
+        cf1 = CrystalField('Ce', 'C2v', **params)
+        cf2 = CrystalField('Pr', 'C2v', **params)
+        cfms = cf1 + cf2
+        cf = 2*cf1 + cf2
+
+        cfms = CrystalFieldMultiSite(Ions=['Ce', 'Pr'], Symmetries=['C2v', 'C2v'], Temperatures=[44.0], FWHMs=[1.1])
+        cfms['ion0.B40'] = -0.031
+        cfms['ion1.B20'] = 0.37737
+        b = cfms['ion0.B22']
+
+        print(b)
+        print(cfms.function)
+
+        cfms = CrystalFieldMultiSite(Ions=['Ce', 'Pr'], Symmetries=['C2v', 'C2v'], Temperatures=[44.0], FWHMs=[1.1],
+                                     parameters={'ion0.B20': 0.37737, 'ion0.B22': 3.9770, 'ion1.B40':-0.031787,
+                                                 'ion1.B42':-0.11611, 'ion1.B44':-0.12544})
+        cfms = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2v', Temperatures=[20], FWHMs=[1.0],
+                                     Background='name=Gaussian,Height=0,PeakCentre=1,Sigma=0;name=LinearBackground,A0=0,A1=0')
+        cfms = CrystalFieldMultiSite(Ions=['Ce'], Symmetries=['C2v'], Temperatures=[50], FWHMs=[0.9],
+                                     Background=LinearBackground(A0=1.0), BackgroundPeak=Gaussian(Height=10, Sigma=0.3))
+        cfms = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2v', Temperatures=[20], FWHMs=[1.0],
+                                     Background= Gaussian(PeakCentre=1) + LinearBackground())
+        cfms = CrystalFieldMultiSite(Ions=['Ce','Pr'], Symmetries=['C2v', 'C2v'], Temperatures=[44, 50], FWHMs=[1.1, 0.9],
+                                     Background=FlatBackground(), BackgroundPeak=Gaussian(Height=10, Sigma=0.3),
+                                     parameters={'ion0.B20': 0.37737, 'ion0.B22': 3.9770, 'ion1.B40':-0.031787,
+                                                 'ion1.B42':-0.11611, 'ion1.B44':-0.12544})
+        cfms.ties({'sp0.bg.f0.Height': 10.1})
+        cfms.constraints('sp0.bg.f0.Sigma > 0.1')
+        cfms.constraints('ion0.sp0.pk1.FWHM < 2.2')
+        cfms.ties({'ion0.sp1.pk2.FWHM': '2*ion0.sp1.pk1.FWHM', 'ion1.sp1.pk3.FWHM': '2*ion1.sp1.pk2.FWHM'})
+
+        # --------------------------
+
+        params = {'ion0.B20': 0.37737, 'ion0.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()
+
+        print(chi2)
+
+        cfms = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2', Temperatures=[25], FWHMs=[1.0], PeakShape='Gaussian',
+                                     BmolX=1.0, B40=-0.02)
+        print(str(cfms.function).split(',')[0])
+
+        # --------------------------
+
+        # Create some crystal field data
+        origin = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, B40=-0.031787, B42=-0.11611, B44=-0.12544,
+                              Temperature=44.0, FWHM=1.1)
+        x, y = origin.getSpectrum()
+        ws = makeWorkspace(x, y)
+
+        # Define a CrystalField object with parameters slightly shifted.
+        cf = CrystalField('Ce', 'C2v', B20=0, B22=0, B40=0, B42=0, B44=0,
+                          Temperature=44.0, FWHM=1.0, ResolutionModel=([0, 100], [1, 1]), FWHMVariation=0)
+
+        # Set any ties on the field parameters.
+        cf.ties(B20=0.37737)
+        # Create a fit object
+        fit = CrystalFieldFit(cf, InputWorkspace=ws)
+        # Find initial values for the field parameters.
+        # You need to define the energy splitting and names of parameters to estimate.
+        # Optionally additional constraints can be set on tied parameters (eg, peak centres).
+        fit.estimate_parameters(EnergySplitting=50,
+                                Parameters=['B22', 'B40', 'B42', 'B44'],
+                                Constraints='20<f1.PeakCentre<45,20<f2.PeakCentre<45',
+                                NSamples=1000)
+        print('Returned', fit.get_number_estimates(), 'sets of parameters.')
+        # The first set (the smallest chi squared) is selected by default.
+        # Select a different parameter set if required
+        fit.select_estimated_parameters(3)
+        print(cf['B22'], cf['B40'], cf['B42'], cf['B44'])
+        # Run fit
+        fit.fit()
+
+        # --------------------------
+
+        from CrystalField import PointCharge
+        axial_pc_model = PointCharge([[-2, 0, 0, -4], [-2, 0, 0, 4]], 'Nd')
+        axial_blm = axial_pc_model.calculate()
+        print(axial_blm)
+
+        from CrystalField import PointCharge
+        from mantid.geometry import CrystalStructure
+        perovskite_structure = CrystalStructure('4 4 4 90 90 90', 'P m -3 m', 'Ce 0 0 0 1 0; Al 0.5 0.5 0.5 1 0; O 0.5 0.5 0 1 0')
+        cubic_pc_model = PointCharge(perovskite_structure, 'Ce', Charges={'Ce':3, 'Al':3, 'O':-2}, MaxDistance=7.5)
+
+        cubic_pc_model = PointCharge(perovskite_structure, 'Ce', Charges={'Ce':3, 'Al':3, 'O':-2}, Neighbour=2)
+        print(cubic_pc_model)
+
+        cif_pc_model = PointCharge('Sm2O3.cif')
+        print(cif_pc_model.getIons())
+
+        cif_pc_model.Charges = {'O1':-2, 'O2':-2, 'Sm1':3, 'Sm2':3, 'Sm3':3}
+        cif_pc_model.IonLabel = 'Sm2'
+        cif_pc_model.Neighbour = 1
+        cif_blm = cif_pc_model.calculate()
+        print(cif_blm)
+        bad_pc_model = PointCharge('Sm2O3.cif', MaxDistance=7.5, Neighbour=2)
+        print(bad_pc_model.Neighbour)
+        print(bad_pc_model.MaxDistance)
+
+        cif_pc_model.Charges = {'O':-2, 'Sm':3}
+        cif_blm = cif_pc_model.calculate()
+        print(cif_blm)
+
+        cf = CrystalField('Sm', 'C2', Temperature=5, FWHM=10, **cif_pc_model.calculate())
+        fit = CrystalFieldFit(cf, InputWorkspace=ws)
+
+        fit = CrystalFieldFit(cf, InputWorkspace=ws)
+        fit.fit()
+
+        # --------------------------
+
+        cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, Temperature=44.0)
+        Cv = cf.getHeatCapacity()       # Calculates Cv(T) for 1<T<300K in 1K steps  (default)
+
+        T = np.arange(1,900,5)
+        Cv = cf.getHeatCapacity(T)      # Calculates Cv(T) for specified values of T (1 to 900K in 5K steps here)
+
+        # Temperatures from a single spectrum workspace
+        ws = CreateWorkspace(T, T, T)
+        Cv = cf.getHeatCapacity(ws)     # Use the x-values of a workspace as the temperatures
+        ws_cp = CreateWorkspace(*Cv)
+
+        # Temperatures from a multi-spectrum workspace
+        ws = CreateWorkspace(T, T, T, NSpec=2)
+        Cv = cf.getHeatCapacity(ws, 1)  # Uses the second spectrum's x-values for T (e.g. 450<T<900)
+
+        chi_v = cf.getSusceptibility(T, Hdir=[1, 1, 1])
+        chi_v_powder = cf.getSusceptibility(T, Hdir='powder')
+        chi_v_cgs = cf.getSusceptibility(T, Hdir=[1, 1, 0], Unit='SI')
+        chi_v_bohr = cf.getSusceptibility(T, Unit='bohr')
+        print(type([chi_v, chi_v_powder, chi_v_cgs, chi_v_bohr]))
+        moment_t = cf.getMagneticMoment(Temperature=T, Hdir=[1, 1, 1], Hmag=0.1) # Calcs M(T) with at 0.1T field||[111]
+        H = np.linspace(0, 30, 121)
+        moment_h = cf.getMagneticMoment(Hmag=H, Hdir='powder', Temperature=10)   # Calcs M(H) at 10K for powder sample
+        moment_SI = cf.getMagneticMoment(H, [1, 1, 1], Unit='SI')         # M(H) in Am^2/mol at 1K for H||[111]
+        moment_cgs = cf.getMagneticMoment(100, Temperature=T, Unit='cgs') # M(T) in emu/mol in a field of 100G || [001]
+        print(type([moment_t, moment_h, moment_SI, moment_cgs]))
+
+        # --------------------------
+
+        from CrystalField import CrystalField, CrystalFieldFit, PhysicalProperties
+        # Fits a heat capacity dataset - you must have subtracted the phonon contribution by some method already
+        # and the data must be in J/mol/K.
+        cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, B40=-0.031787, B42=-0.11611, B44=-0.12544,
+                          PhysicalProperty=PhysicalProperties('Cv'))
+        fitcv = CrystalFieldFit(Model=cf, InputWorkspace=ws)
+        fitcv.fit()
+
+        params = {'B20':0.37737, 'B22':3.9770, 'B40':-0.031787, 'B42':-0.11611, 'B44':-0.12544}
+        cf = CrystalField('Ce', 'C2v', **params)
+        cf.PhysicalProperty = PhysicalProperties('Cv')
+        fitcv = CrystalFieldFit(Model=cf, InputWorkspace=ws)
+        fitcv.fit()
+
+        # Fits a susceptibility dataset. Data is the volume susceptibility in SI units
+        cf = CrystalField('Ce', 'C2v', **params)
+        cf.PhysicalProperty = PhysicalProperties('susc', Hdir='powder', Unit='SI')
+        fit_chi = CrystalFieldFit(Model=cf, InputWorkspace=ws)
+        fit_chi.fit()
+
+        # Fits a magnetisation dataset. Data is in emu/mol, and was measured at 5K with the field || [111].
+        cf = CrystalField('Ce', 'C2v', **params)
+        cf.PhysicalProperty = PhysicalProperties('M(H)', Temperature=5, Hdir=[1, 1, 1], Unit='cgs')
+        fit_mag = CrystalFieldFit(Model=cf, InputWorkspace=ws)
+        fit_mag.fit()
+
+        # Fits a magnetisation vs temperature dataset. Data is in Am^2/mol, measured with a 0.1T field || [110]
+        cf = CrystalField('Ce', 'C2v', **params)
+        cf.PhysicalProperty = PhysicalProperties('M(T)', Hmag=0.1, Hdir=[1, 1, 0], Unit='SI')
+        fit_moment = CrystalFieldFit(Model=cf, InputWorkspace=ws)
+        fit_moment.fit()
+
+        # --------------------------
+
+        # Pregenerate the required workspaces
+        for tt in [10, 44, 50]:
+            cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, B40=-0.031787, B42=-0.11611, B44=-0.12544, Temperature=tt, FWHM=0.5)
+            x, y = cf.getSpectrum()
+            self.my_create_ws('ws_ins_'+str(tt)+'K', x, y)
+        ws_ins_10K = mtd['ws_ins_10K']
+        ws_ins_44K = mtd['ws_ins_44K']
+        ws_ins_50K = mtd['ws_ins_50K']
+        ws_cp = self.my_create_ws('ws_cp', *cf.getHeatCapacity())
+        ws_chi = self.my_create_ws('ws_chi', *cf.getSusceptibility(np.linspace(1,300,100), Hdir='powder', Unit='cgs'))
+        ws_mag = self.my_create_ws('ws_mag', *cf.getMagneticMoment(Hmag=np.linspace(0, 30, 100), Hdir=[1,1,1], Unit='bohr', Temperature=5))
+
+        # --------------------------
+
+        # Fits an INS spectrum (at 10K) and the heat capacity simultaneously
+        cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, B40=-0.031787, B42=-0.11611, B44=-0.12544)
+        cf.Temperature = 10
+        cf.FWHM = 1.5
+        cf.PhysicalProperty = PhysicalProperties('Cv')
+        fit = CrystalFieldFit(Model=cf, InputWorkspace=[ws_ins_10K, ws_cp])
+        fit.fit()
+
+        # Fits two INS spectra (at 44K and 50K) and the heat capacity, susceptibility and magnetisation simultaneously.
+        PPCv = PhysicalProperties('Cv')
+        PPchi = PhysicalProperties('susc', 'powder', Unit='cgs')
+        PPMag = PhysicalProperties('M(H)', [1, 1, 1], 5, 'bohr')
+        cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, B40=-0.031787, B42=-0.11611, B44=-0.12544,
+                          Temperature=[44.0, 50], FWHM=[1.1, 0.9], PhysicalProperty=[PPCv, PPchi, PPMag] )
+        fit = CrystalFieldFit(Model=cf, InputWorkspace=[ws_ins_44K, ws_ins_50K, ws_cp, ws_chi, ws_mag])
+        fit.fit()
diff --git a/docs/source/fitfunctions/CrystalFieldSusceptibility.rst b/docs/source/fitfunctions/CrystalFieldSusceptibility.rst
index 9820e65ea8f47e8696a077b2073d188cf923a77c..f06644e26df6d1765bfdc3857a0e222771559408 100644
--- a/docs/source/fitfunctions/CrystalFieldSusceptibility.rst
+++ b/docs/source/fitfunctions/CrystalFieldSusceptibility.rst
@@ -31,14 +31,15 @@ be along the quantisation axis of the crystal field (which is usually defined to
 :math:`B_x`, :math:`B_y`, and :math:`B_z` are the components of the unit vector pointing in the direction of the applied 
 magnetic field in this coordinate system.
 
-Finally, in order to account for the effect of any exchange interactions in the system which will shift the susceptibility curve
-up or down (analogous to the Curie-Weiss temperature), the actual magnetic susceptibility calculated by this function is:
+Finally, in order to account for the effect of any exchange interactions in the system which will shift the susceptiblity curve
+up or down (analogous to the Curie-Weiss temperature), and any residual (background) susceptibility in the sample (perhaps from
+an impurity), the actual magnetic susceptibility calculated by this function is:
 
-.. math:: \chi^{\mathrm{eff}} = \frac{\chi(T)}{1 - \lambda \chi(T)}
+.. math:: \chi^{\mathrm{eff}} = \frac{\chi(T)}{1 - \lambda \chi(T)} + \chi_0
 
-where :math:`\lambda` parameterises an effective exchange interaction and :math:`\chi` is the bare (paramagnetic Crystal Field)
-susceptibility. A negative :math:`\lambda` indicates overall antiferromagnetic interactions, whilst a positive :math:`\lambda`
-corresponds to overall ferromagnetic interactions.
+where :math:`\lambda` parameterises an effective exchange interaction with :math:`\chi` the bare (paramagnetic Crystal Field)
+susceptibility, and :math:`\chi_0` the residual susceptibility. A negative :math:`\lambda` indicates overall antiferromagnetic
+interactions, whilst a positive :math:`\lambda` corresponds to overall ferromagnetic interactions.
 
 Example
 -------
diff --git a/docs/source/interfaces/Crystal Field Python Interface.rst b/docs/source/interfaces/Crystal Field Python Interface.rst
index 461865af1b369b6d0a2ae94c0b93d8f7f2b09ec8..2dc1d1bf1a38283327bcbb37c8182965098d2b27 100644
--- a/docs/source/interfaces/Crystal Field Python Interface.rst	
+++ b/docs/source/interfaces/Crystal Field Python Interface.rst	
@@ -7,18 +7,18 @@ Crystal Field Python Interface
   :local:
 
 The python facilities for Crystal Field calculations are available in Mantid from module `CrystalField`.
-There are two main classes the module provides: `CrystalFiled` that defines various properties of a crystal
-field and `CrystalFieldFit` that manages the fitting process.
+The module provides two main classes: `CrystalField` defines various properties of a crystal field and
+`CrystalFieldFit` manages the fitting process.
 
 
 Setting up crystal field parameters
 -----------------------------------
 
 A crystal field computation starts with creating an instance of the `CrystalField` class. The constructor
-has two mandatory arguments: `Ion` - a symbolic name of the ion, and `Symmetry` - a name of the symmetry group
-of the field. The rest of the parameters are optional.
+has two mandatory arguments: `Ion` - the symbolic name of the ion, and `Symmetry` - the name of the point symmetry
+group of the field. The rest of the parameters are optional.
 
-Possible values for the `Ion` argument::
+Possible values for the `Ion` argument are::
 
  Ce, Pr, Nd, Pm, Sm, Eu, Gd, Tb, Dy, Ho, Er, Tm, Yb
 
@@ -30,7 +30,7 @@ or half-integer value, e.g. `Ion=S2` or `Ion=S1.5`. In these cases, the g-factor
 The prefix letter can also be `J` instead of `S`, and lower case letters are also supported. (e.g. `Ion=j1`,
 `Ion=s2.5` and `Ion=J0.5` are all valid).
  
-Allowed values for `Symmetry`::
+Allowed values for `Symmetry` are::
 
   C1, Ci, C2, Cs, C2h, C2v, D2, D2h, C4, S4, C4h, D4, C4v, D2d, D4h, C3,
   S6, D3, C3v, D3d, C6, C3h, C6h, D6, C6v, D3h, D6h, T, Td, Th, O, Oh
@@ -41,7 +41,7 @@ The minimum code to create a crystal field object is::
   cf = CrystalField('Ce', 'C2v')
   
 Names of the crystal field parameters have the form `Bnn` and `IBnn` where `nn` are two digits between 0 and 6.
-The `Bnn` is a real and the `IBnn` is an imaginary part of a complex parameter. If a parameter isn't set explicitly
+`Bnn` is the real and `IBnn` is the imaginary part of a complex parameter. If a parameter isn't set explicitly
 its default value is 0. To set a parameter pass it to the `CrystalField` constructor as a keyword argument, e.g.::
 
   cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770)
@@ -58,7 +58,7 @@ Which can also be used to query the value of a parameter::
 Calculating the Eigensystem
 ---------------------------
 
-`CrystalField` class has methods to calculate the Hamiltonian and its eigensystem::
+The `CrystalField` class has methods to calculate the Hamiltonian and its eigensystem::
 
   # Calculate and return the Hamiltonian matrix as a 2D numpy array.
   h = cf.getHamiltonian()
@@ -91,7 +91,7 @@ Knowing the temperature allows us to calculate a peak list: a list of transition
 
   print cf.getPeakList()
   
-The output is::
+Which produces the output::
 
  [[  0.00000000e+00   2.44006198e+01   4.24977124e+01   1.80970926e+01 -2.44006198e+01]
   [  2.16711565e+02   8.83098530e+01   5.04430056e+00   1.71153708e-01  1.41609425e-01]]
@@ -112,9 +112,9 @@ The new output::
  [[   0.           24.40061976   42.49771237]
   [ 216.71156467   88.30985303    5.04430056]]
   
-To calculate a spectrum we need to define a shape of each peak (peak profile function) and its default width (`FWHM`).
+To calculate a spectrum we need to define the shape of each peak (peak profile function) and its default width (`FWHM`).
 The width can be set either via a keyword argument or a property with name `FWHM`. If the peak shape isn't set the default
-of Lorentzian is assumed. To set a different shape use the `PeakShape` property::
+of `Lorentzian` is assumed. To set a different shape use the `PeakShape` property::
 
   cf.PeakShape = 'Gaussian'
   cf.FWHM = 0.9
@@ -128,8 +128,9 @@ After the peak shape is defined a spectrum can be calculated::
   
 The output is a tuple of two 1d numpy arrays (x, y) that can be used with `matplotlib` to plot::
 
-  pyplot.plot(*sp)
-  pyplot.show()
+  import matplotlib.pyplot as plt
+  plt.plot(*sp)
+  plt.show()
   
 .. image:: /images/CrystalFieldSpectrum1.png
    :height: 300
@@ -170,7 +171,13 @@ Plotting in MantidPlot
 ----------------------
 
 To plot a spectrum using MantidPlot's graphing facilities `CrystalField` has method `plot`. It has the same arguments as `getSpectrum`
-and opens a window with a plot.
+and opens a window with a plot, e.g.::
+
+  cf.plot()
+
+In addition to plotting, the `plot` method creates a workspace named `CrystalField_<Ion>` with the plot data. Subsequent calls to `plot` 
+for the same `CrystalField` object will use the same plot window as created by the first call unless this window has been closed in the 
+mean time.
 
 
 Adding a Background
@@ -194,7 +201,7 @@ Setting Ties and Constraints
 ----------------------------
 
 Setting ties and constraints are done by calling the `ties` and `constraints` methods of the `CrystalField` class or its components.
-To `Bnn` parameters are tied by the `CrystalField` class directly specifying the tied parameter as a keyword argument::
+The `Bnn` parameters are tied by the `CrystalField` class directly specifying the tied parameter as a keyword argument::
 
   cf.ties(B20=1.0, B40='B20/2')
   
@@ -238,14 +245,16 @@ which is equivalent to::
 Setting Resolution Model
 ------------------------
 
-Resolution model here is a way to constrain widths of the peaks to realistic numbers which agree with a measured or
+A resolution model is a way to constrain the widths of the peaks to realistic numbers which agree with a measured or
 calculated instrument resolution function. A model is a function that returns a FWHM for a peak centre. The Crystal
-Field python interface defines helper class `ResolutionModel` to help define and set resolution models.
+Field python interface defines the helper class `ResolutionModel` to help define and set resolution models.
 
 To construct an instance of `ResolutionModel` one needs to provide up to four input parameters. The first parameter, `model`, is
-mandatory and can be either of the two
+mandatory and can be either of:
 
-1. A tuple containing two arrays (lists) of real numbers which will be interpreted as tabulated values of the model function. The first element of the tuple is a list of increasing values for peak centres, and the second element is a list of corresponding widths. Values between the tabulated peak positions will be linearly interpolated.
+1. A tuple containing two arrays (lists) of real numbers which will be interpreted as tabulated values of the model function. 
+   The first element of the tuple is a list of increasing values for peak centres, and the second element is a list of corresponding
+   widths. Values between the tabulated peak positions will be linearly interpolated.
 
 2. A python function that takes a :class:`numpy.ndarray` of peak positions and returns a numpy array of widths.
 
@@ -254,21 +263,38 @@ function. `xstart` and `xend` define the interval of interpolation which must in
 :math:`10^{-4}` and defines an approximate desired accuracy of the approximation. The interval will be split until the largest error of the interpolation
 is smaller than `accuracy`. Note that subdivision cannot go on to infinity as the number of points is limited by the class member `ResolutionModel.max_model_size`.
 
-Example of setting a resolution model::
+Example of setting a resolution model using a tuple of two arrays::
 
+    from CrystalField import CrystalField, ResolutionModel
     rm = ResolutionModel(([1, 2, 3, ...., 100], [0.1, 0.3, 0.35, ..., 2.1]))
     cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, ..., Temperature=44.0, ResolutionModel=rm)
 
-    ...
+Or using an arbitrary function `my_func`::
+
+    def my_func(en):
+        return (25-en)**(1.5) / 200 + 0.1
+
+    rm = ResolutionModel(my_func, xstart=0.0, xend=24.0, accuracy=0.01)
+    cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, ..., Temperature=44.0, ResolutionModel=rm)
+
+Finally, the :ref:`PyChop` interface may be used to generate the resolution function for a particular spectrometer::
 
-    rm = ResolutionModel(my_func, xstart=0.0, xend=120.0, accuracy=0.01)
+    from PyChop import PyChop2
+    marires = PyChop2('MARI')
+    marires.setChopper('S')
+    marires.setFrequency(250)
+    marires.setEi(30)
+    rm = ResolutionModel(marires.getResolution, xstart=0.0, xend=29.0, accuracy=0.01)
     cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, ..., Temperature=44.0, ResolutionModel=rm)
 
-When a resolution model is set the peak width will be constrained to have a value close to the model. The degree of deviation is controlled by the
-`FWHMVariation` parameter. It has the default of 0.1 and is an absolute maximum difference a width can have. If set to 0 the widths will be fixed
-to their calculated values (depending on the instant values of their peak centres). For example::
+When a resolution model is set, the peak width will be constrained to have a value close to the model. The degree of deviation is controlled by the
+`FWHMVariation` parameter. It has the default of 0.1 and is the maximum difference from the value given by the resolution model a width can have. 
+If set to 0 the widths will be fixed to their calculated values (depending on the instant values of their peak centres). For example::
 
-    cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, ..., Temperature=44.0, ResolutionModel=rm, FWHMVariation=0.001)
+    cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, ..., Temperature=44.0, ResolutionModel=rm, FWHMVariation=0.1)
+
+will allow the peak widths to vary between :math:`\Delta(E)-0.1` and :math:`\Delta(E)+0.1` where :math:`\Delta(E)` is the value of the 
+resolution model at the peak position :math:`E`.
 
 
 
@@ -311,22 +337,22 @@ The resolution model also needs to be initialised from a list::
     # or
 
     rm = ResolutionModel([func0, func1], 0, 100, accuracy = 0.01)
-
+    cf.ResolutionModel = rm
 
 To calculate a spectrum call the same method `getSpectrum` but pass the spectrum index as its first parameter::
 
-  # Calculate second spectrum, use the generated x-values
-  sp = cf.getSpectrum(1)
+    # Calculate second spectrum, use the generated x-values
+    sp = cf.getSpectrum(1)
 
-  # Calculate third spectrum, use a list for x-values
-  x = [0, 1, 2, 3, ...]
-  sp = cf.getSpectrum(2, x)
-  
-  # Calculate second spectrum, use the first spectrum of a workspace
-  sp = cf.getSpectrum(1, ws)
-  
-  # Calculate first spectrum, use the i-th spectrum of a workspace
-  sp = cf.getSpectrum(0, ws, i)
+    # Calculate third spectrum, use a list for x-values
+    x = [0, 1, 2, 3, ...]
+    sp = cf.getSpectrum(2, x)
+    
+    # Calculate second spectrum, use the first spectrum of a workspace
+    sp = cf.getSpectrum(1, ws)
+    
+    # Calculate first spectrum, use the i-th spectrum of a workspace
+    sp = cf.getSpectrum(0, ws, i)
 
 Note that the attributes `Temperature`, `FWHM`, `peaks` and `background` may be set separately from the constructor, e.g.::
 
@@ -380,9 +406,11 @@ means that the intensity of `cf1` should be twice that of `cf2`.
 
 Alternatively, you can create a `CrystalFieldMultiSite` object directly. This takes Ions, Symmetries, Temperatures and peak widths as lists::
 
+    from CrystalField import CrystalFieldMultiSite
     cfms = CrystalFieldMultiSite(Ions=['Ce', 'Pr'], Symmetries=['C2v', 'C2v'], Temperatures=[44.0], FWHMs=[1.1])
 
-To access parameters of a CrystalFieldMultiSite object, prefix with the ion index::
+Note that `Temperature` and `FWHM` (without plural) can also be used in place of the equivalent plural parameters.
+To access parameters of a CrystalFieldMultiSite object, prefix them with the ion index::
 
     cfms['ion0.B40'] = -0.031
     cfms['ion1.B20'] = 0.37737
@@ -393,7 +421,7 @@ Parameters can be set when creating the object by passing in a dictionary using
 
     cfms = CrystalFieldMultiSite(Ions=['Ce', 'Pr'], Symmetries=['C2v', 'C2v'], Temperatures=[44.0], FWHMs=[1.1],
                                  parameters={'ion0.B20': 0.37737, 'ion0.B22': 3.9770, 'ion1.B40':-0.031787,
-                                               'ion1.B42':-0.11611, 'ion1.B44':-0.12544})
+                                             'ion1.B42':-0.11611, 'ion1.B44':-0.12544})
 
 A background can also be set this way, or using `cfms.background.` It can be passed as a string, a Function object(s), or a 
 CompositeFunction object::
@@ -407,7 +435,7 @@ CompositeFunction object::
     cfms = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2v', Temperatures=[20], FWHMs=[1.0],
                                    Background= Gaussian(PeakCentre=1) + LinearBackground())
 
-Ties and constraints are set similiarly to `CrystalField` objects. `f` prefixes have been changed to be more descriptive::
+Ties and constraints are set similarly to `CrystalField` objects. `f` prefixes have been changed to be more descriptive::
 
     cfms = CrystalFieldMultiSite(Ions=['Ce','Pr'], Symmetries=['C2v', 'C2v'], Temperatures=[44, 50], FWHMs=[1.1, 0.9],
                                    Background=FlatBackground(), BackgroundPeak=Gaussian(Height=10, Sigma=0.3),
@@ -418,8 +446,11 @@ Ties and constraints are set similiarly to `CrystalField` objects. `f` prefixes
     cfms.constraints('ion0.sp0.pk1.FWHM < 2.2')
     cfms.ties({'ion0.sp1.pk2.FWHM': '2*ion0.sp1.pk1.FWHM', 'ion1.sp1.pk3.FWHM': '2*ion1.sp1.pk2.FWHM'})
 
-When fitting, all parameters are assumed to be free. Parameters must be fixed explicitly. Fitting example::
+Parameters which are not allowed by the specified symmetry will be fixed to be zero, but unlike for the single-site case,
+all other parameters are assumed to be free (in the single-site case, parameters which are unset are assumed to be fixed
+to be zero). For the multi-site case, parameters must be fixed explicitly. For example::
 
+    params = {'ion0.B20': 0.37737, 'ion0.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)
@@ -567,14 +598,14 @@ gives::
 
     {'O1': [0.125, 0.125, 0.375],
      'O2': [0.125, 0.375, 0.375],
-     'Yb1': [0.25, 0.25, 0.25],
-     'Yb2': [0.021, 0.0, 0.25],
-     'Yb3': [0.542, 0.0, 0.25]}
+     'Sm1': [0.25, 0.25, 0.25],
+     'Sm2': [0.021, 0.0, 0.25],
+     'Sm3': [0.542, 0.0, 0.25]}
 
 You can then define the charges for each site, the magnetic ion and the maximum distance, and calculate::
 
-    cif_pc_model.Charges = {'O1':-2, 'O2':-2, 'Yb1':3, 'Yb2':3, 'Yb3':3}
-    cif_pc_model.IonLabel = 'Yb2'
+    cif_pc_model.Charges = {'O1':-2, 'O2':-2, 'Sm1':3, 'Sm2':3, 'Sm3':3}
+    cif_pc_model.IonLabel = 'Sm2'
     cif_pc_model.Neighbour = 1
     cif_blm = cif_pc_model.calculate()
     print(cif_blm)
@@ -591,14 +622,14 @@ then the value for ``MaxDistance`` will be used regardless of where it appears i
 
 For ``Charges``, instead of listing the charges of each site, you can just give the charge for each element, e.g.::
 
-    cif_pc_model.Charges = {'O':-2, 'Yb':3}
+    cif_pc_model.Charges = {'O':-2, 'Sm':3}
     cif_blm = cif_pc_model.calculate()
 
 The result of the ``calculate()`` method can be put directly into a ``CrystalField`` object and used either
 to calculate a spectrum or as the starting parameters in a fit::
 
-    cf = CrystalField('Yb', 'C2', Temperature=5, FWHM=10, **cif_pc_model.calculate())
-    plot(**cf.getSpectrum())
+    cf = CrystalField('Sm', 'C2', Temperature=5, FWHM=10, **cif_pc_model.calculate())
+    plot(*cf.getSpectrum())
     fit = CrystalFieldFit(cf, InputWorkspace=ws)
     fit.fit()
 
@@ -620,12 +651,14 @@ susceptibility, and magnetisation. The calculated values can be invoked using th
 
 To calculate the heat capacity use::
 
+    import matplotlib.pyplot as plt
+    cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, Temperature=44.0)
     Cv = cf.getHeatCapacity()       # Calculates Cv(T) for 1<T<300K in 1K steps  (default)
-    pyplot.plot(*Cv)                # Returns a tuple of (x, y) values
+    plt.plot(*Cv)                   # Returns a tuple of (x, y) values
 
     T = np.arange(1,900,5)
     Cv = cf.getHeatCapacity(T)      # Calculates Cv(T) for specified values of T (1 to 900K in 5K steps here)
-    pyplot.plot(T, Cv[1])
+    plt.plot(T, Cv[1])
 
     # Temperatures from a single spectrum workspace
     ws = CreateWorkspace(T, T, T)
@@ -710,22 +743,26 @@ class as the `PhysicalProperty` attribute of `CrystalField`, either as a keyword
 
 or separately after construction::
 
-    cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, B40=-0.031787, B42=-0.11611, B44=-0.12544)
+    params = {'B20':0.37737, 'B22':3.9770, 'B40':-0.031787, 'B42':-0.11611, 'B44':-0.12544}
+    cf = CrystalField('Ce', 'C2v', **params)
     cf.PhysicalProperty = PhysicalProperties('Cv')
     fitcv = CrystalFieldFit(Model=cf, InputWorkspace=ws)
     fitcv.fit()
 
     # Fits a susceptibility dataset. Data is the volume susceptibility in SI units
+    cf = CrystalField('Ce', 'C2v', **params)
     cf.PhysicalProperty = PhysicalProperties('susc', Hdir='powder', Unit='SI')
     fit_chi = CrystalFieldFit(Model=cf, InputWorkspace=ws)
     fit_chi.fit()
 
     # Fits a magnetisation dataset. Data is in emu/mol, and was measured at 5K with the field || [111].
+    cf = CrystalField('Ce', 'C2v', **params)
     cf.PhysicalProperty = PhysicalProperties('M(H)', Temperature=5, Hdir=[1, 1, 1], Unit='cgs')
     fit_mag = CrystalFieldFit(Model=cf, InputWorkspace=ws)
     fit_mag.fit()
 
     # Fits a magnetisation vs temperature dataset. Data is in Am^2/mol, measured with a 0.1T field || [110]
+    cf = CrystalField('Ce', 'C2v', **params)
     cf.PhysicalProperty = PhysicalProperties('M(T)', Hmag=0.1, Hdir=[1, 1, 0], Unit='SI')
     fit_moment = CrystalFieldFit(Model=cf, InputWorkspace=ws)
     fit_moment.fit()
@@ -754,9 +791,9 @@ properties should be a list. E.g.::
     fit.fit()
 
     # Fits two INS spectra (at 44K and 50K) and the heat capacity, susceptibility and magnetisation simultaneously.
-    PPCv = PhysicalProperty('Cv')
-    PPchi = PhysicalProperty('susc', 'powder', Unit='cgs')
-    PPMag = PhysicalProperty('M(H)', 5, [1, 1, 1], 'bohr')
+    PPCv = PhysicalProperties('Cv')
+    PPchi = PhysicalProperties('susc', 'powder', Unit='cgs')
+    PPMag = PhysicalProperties('M(H)', [1, 1, 1], 5, 'bohr')
     cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, B40=-0.031787, B42=-0.11611, B44=-0.12544,
                       Temperature=[44.0, 50], FWHM=[1.1, 0.9], PhysicalProperty=[PPCv, PPchi, PPMag] )
     fit = CrystalFieldFit(Model=cf, InputWorkspace=[ws_ins_44K, ws_ins_50K, ws_cp, ws_chi, ws_mag])
@@ -764,7 +801,7 @@ properties should be a list. E.g.::
 
 Note that `PhysicalProperty` requires the type of physical property (either `'Cv'` or `'Cp'` or `'heatcap'` for
 heat capacity; `'susc'` or `'chi'` for susceptibility; `'mag'` or `'M(H)'` for magnetic moment vs applied field;
-or `'mom'` or `'M(T)'` for moment vs temperature) as the first argument. subsequent arguments are optional, and
+or `'mom'` or `'M(T)'` for moment vs temperature) as the first argument. Subsequent arguments are optional, and
 are in the following order::
 
     PhysicalProperties('Cp')  # No further parameters required for heat capacity
diff --git a/docs/source/release/v3.12.0/direct_inelastic.rst b/docs/source/release/v3.12.0/direct_inelastic.rst
index 1949d7a42fb508f15d9e3e929330f700345edfdf..3db959ac18f8b2ae6732015484109151c990c6c8 100644
--- a/docs/source/release/v3.12.0/direct_inelastic.rst
+++ b/docs/source/release/v3.12.0/direct_inelastic.rst
@@ -27,6 +27,12 @@ Algorithms
 Crystal Field
 #############
 
+Multi-site calculations and fitting are now supported by the crystal field (Python commandline) interface. 
+
+Calculation of dipole transition matrix elements has been added, together with the addition of a :math:`\chi_0` term in the :ref:`CrystalFieldSusceptibility <func-CrystalFieldSusceptibility>` function. 
+
+Several bugs in the Python and C++ code has been fixed - see the `github page <https://github.com/mantidproject/mantid/pull/21604>`_ for details.
+
 Features Removed
 ----------------
 
diff --git a/docs/source/release/v3.12.0/spectroscopy.rst b/docs/source/release/v3.12.0/spectroscopy.rst
index c327a21767bbd88294b14be30836de85e2202f16..e19120bf466500cda86a7a1eb4fb099ed38830c6 100644
--- a/docs/source/release/v3.12.0/spectroscopy.rst
+++ b/docs/source/release/v3.12.0/spectroscopy.rst
@@ -11,6 +11,7 @@ Spectroscopy Changes
 
 - The algorithms :ref:`algm-SofQWCentre`, :ref:`algm-SofQWPolygon` and :ref:`algm-SofQWNormalisedPolygon`, which rebin an inelastic workspace (has a `DeltaE` axis) from spectrum numbers (angle) to `MomentumTransfer` may now rebin the energy (`DeltaE`) axis as well as the :math:`|Q|` (`MomentumTransfer`) axes.
 - :ref:`algm-SofQWNormalisedPolygon` now has uses a faster method for calculating the polygon intersections.
+- The crystal field computation and fitting engine is now feature complete. It can now handle multi-site computation and simultaneous fitting of inelastic spectra and physical properties dataset. See the :ref:`Crystal Field Python Interface` help page for details, and `<http://www.mantidproject.org/Crystal_Field_Examples>`_ for examples of use.
 
 Direct Geometry
 ---------------
diff --git a/scripts/Inelastic/CrystalField/CrystalFieldMultiSite.py b/scripts/Inelastic/CrystalField/CrystalFieldMultiSite.py
index 5c693ea580b2229c067402a52b5db65e63ba5541..2eb89e593e877772a820f123ec393f6244c3a1d2 100644
--- a/scripts/Inelastic/CrystalField/CrystalFieldMultiSite.py
+++ b/scripts/Inelastic/CrystalField/CrystalFieldMultiSite.py
@@ -1,6 +1,7 @@
 import numpy as np
 
 from CrystalField import CrystalField, Function
+from .fitting import islistlike
 
 
 def makeWorkspace(xArray, yArray, child=True, ws_name='dummy'):
@@ -66,9 +67,13 @@ class CrystalFieldMultiSite(object):
         self.Symmetries = Symmetries
         self._plot_window = {}
         self.chi2 = None
+        self._resolutionModel = None
 
         parameter_dict = kwargs.pop('parameters', None)
         attribute_dict = kwargs.pop('attributes', None)
+        ties_dict = kwargs.pop('ties', None)
+        constraints_list = kwargs.pop('constraints', None)
+        fix_list = kwargs.pop('fixedParameters', None)
 
         kwargs = self._setMandatoryArguments(kwargs)
 
@@ -78,22 +83,32 @@ class CrystalFieldMultiSite(object):
 
         self._setRemainingArguments(kwargs)
 
+        self.default_spectrum_size = 200
+
         if attribute_dict is not None:
             for name, value in attribute_dict.items():
                 self.function.setAttributeValue(name, value)
         if parameter_dict is not None:
             for name, value in parameter_dict.items():
                 self.function.setParameter(name, value)
+        if ties_dict:
+            for name, value in parameter_dict.items():
+                self.function.tie(name, value)
+        if constraints_list:
+            self.function.addConstraints(','.join(constraints_list))
+        if fix_list:
+            for param in fix_list:
+                self.function.fixParameter(param)
 
     def _setMandatoryArguments(self, kwargs):
-        if 'Temperatures' in kwargs:
-            self.Temperatures = kwargs.pop('Temperatures')
-            if 'FWHMs' in kwargs:
-                self.FWHMs = kwargs.pop('FWHMs')
+        if 'Temperatures' in kwargs or 'Temperature' in kwargs:
+            self.Temperatures = kwargs.pop('Temperatures') if 'Temperatures' in kwargs else kwargs.pop('Temperature')
+            if 'FWHM' in kwargs or 'FWHMs' in kwargs:
+                self.FWHM = kwargs.pop('FWHMs') if 'FWHMs' in kwargs else kwargs.pop('FWHM')
             elif 'ResolutionModel' in kwargs:
                 self.ResolutionModel = kwargs.pop('ResolutionModel')
             else:
-                raise RuntimeError("If temperatures are set, must also set FWHMs or ResolutionModel")
+                raise RuntimeError("If temperatures are set, must also set FWHM or ResolutionModel")
         return kwargs
 
     def _setRemainingArguments(self, kwargs):
@@ -137,6 +152,16 @@ class CrystalFieldMultiSite(object):
 
         return self._calcSpectrum(i, ws, ws_index)
 
+    def calc_xmin_xmax(self, i=0):
+        peaks = np.array([])
+        for idx in range(len(self.Ions)):
+            blm = {}
+            for bparam in CrystalField.field_parameter_names:
+                blm[bparam] = self.function.getParameterValue('ion{}.'.format(idx) + bparam)
+            _cft = CrystalField(self.Ions[idx], 'C1', Temperature=self.Temperatures[i], **blm)
+            peaks = np.append(peaks, _cft.getPeakList()[0])
+        return np.min(peaks), np.max(peaks)
+
     def getSpectrum(self, *args):
         """
         Get a specified spectrum calculated with the current field and peak parameters.
@@ -144,14 +169,16 @@ class CrystalFieldMultiSite(object):
         Alternatively can be called getSpectrum(workspace, ws_index). Spectrum index is assumed zero.
 
         Examples:
+            cf.getSpectrum()         # Calculate the first spectrum using automatically generated x-values
+            cf.getSpectrum(1)        # Calculate the second spectrum using automatically generated x-values
             cf.getSpectrum(1, ws, 5) # Calculate the second spectrum using the x-values from the 6th spectrum
                                      # in workspace ws.
-            cf.getSpectrum(ws) # Calculate the first spectrum using the x-values from the 1st spectrum
-                               # in workspace ws.
-            cf.getSpectrum(ws, 3) # Calculate the first spectrum using the x-values from the 4th spectrum
-                                  # in workspace ws.
-            cf.getSpectrum(3, ws) # Calculate the third spectrum using the x-values from the 1st spectrum
-                                  # in workspace ws.
+            cf.getSpectrum(ws)       # Calculate the first spectrum using the x-values from the 1st spectrum
+                                     # in workspace ws.
+            cf.getSpectrum(ws, 3)    # Calculate the first spectrum using the x-values from the 4th spectrum
+                                     # in workspace ws.
+            cf.getSpectrum(2, ws)    # Calculate the third spectrum using the x-values from the 1st spectrum
+                                     # in workspace ws.
 
         @return: A tuple of (x, y) arrays
         """
@@ -160,11 +187,18 @@ class CrystalFieldMultiSite(object):
                 raise RuntimeError('You must first define a temperature for the spectrum')
             return self._calcSpectrum(args[0], args[1], args[2])
         elif len(args) == 1:
-            return self._calcSpectrum(0, args[0], 0)
+            if isinstance(args[0], int):
+                x_min, x_max = self.calc_xmin_xmax(args[0])
+                xArray = np.linspace(x_min, x_max, self.default_spectrum_size)
+                return self._calcSpectrum(args[0], xArray, 0)
+            else:
+                return self._calcSpectrum(0, args[0], 0)
         elif len(args) == 2:
             return self._getSpectrumTwoArgs(*args)
         else:
-            raise RuntimeError('getSpectrum expected 1-3 arguments, got {}s'.format(len(args)))
+            x_min, x_max = self.calc_xmin_xmax()
+            xArray = np.linspace(x_min, x_max, self.default_spectrum_size)
+            return self._calcSpectrum(0, xArray, 0)
 
     def _convertToWS(self, wksp_list):
         """
@@ -350,7 +384,7 @@ class CrystalFieldMultiSite(object):
         params = get_parameters_for_add_from_multisite(self, 0)
         params.update(get_parameters_for_add_from_multisite(other, len(self.Ions)))
         new_cf = CrystalFieldMultiSite(Ions=ions, Symmetries=symmetries, Temperatures=self.Temperatures,
-                                       FWHMs=self.FWHMs, parameters=params, abundances=abundances)
+                                       FWHM=self.FWHM, parameters=params, abundances=abundances)
         return new_cf
 
     def __getitem__(self, item):
@@ -378,7 +412,7 @@ class CrystalFieldMultiSite(object):
         params = get_parameters_for_add_from_multisite(self, 0)
         params.update(get_parameters_for_add(other, len(self.Ions)))
         new_cf = CrystalFieldMultiSite(Ions=ions, Symmetries=symmetries, Temperatures=self.Temperatures,
-                                       FWHMs=self.FWHMs, parameters=params, abundances=abundances)
+                                       FWHM=self.FWHM, parameters=params, abundances=abundances)
         return new_cf
 
     def __radd__(self, other):
@@ -395,13 +429,31 @@ class CrystalFieldMultiSite(object):
         params = get_parameters_for_add(other, 0)
         params.update(get_parameters_for_add_from_multisite(self, 1))
         new_cf = CrystalFieldMultiSite(Ions=ions, Symmetries=symmetries, Temperatures=self.Temperatures,
-                                       FWHMs=self.FWHMs, parameters=params, abundances=abundances)
+                                       FWHM=self.FWHM, parameters=params, abundances=abundances)
         return new_cf
 
     @property
     def background(self):
         return self._background
 
+    @background.setter
+    def background(self, value):
+        if hasattr(value, 'peak') and hasattr(value, 'background'):
+            # Input is a CrystalField.Background object
+            if value.peak and value.background:
+                self._setBackground(peak=str(value.peak.function), background=str(value.background.function))
+            elif value.peak:
+                self._setBackground(peak=str(value.peak.function))
+            else:
+                self._setBackground(background=str(value.background.function))
+        elif hasattr(value, 'function'):
+            self._setBackground(background=str(value.function))
+        else:
+            self._setBackground(background=value)
+        # Need this for a weird python bug: "IndexError: Function index (2) out of range (2)"
+        # if user calls print(self.function) after setting background
+        _ = self.function.getTies() # noqa: F841
+
     @property
     def Ions(self):
         string_ions = self.function.getAttributeValue('Ions')
@@ -456,6 +508,14 @@ class CrystalFieldMultiSite(object):
     def Temperatures(self, value):
         self.function.setAttributeValue('Temperatures', value)
 
+    @property
+    def Temperature(self):
+        return list(self.function.getAttributeValue("Temperatures"))
+
+    @Temperature.setter
+    def Temperatures(self, value):
+        self.function.setAttributeValue('Temperatures', value)
+
     @property
     def FWHMs(self):
         fwhm = self.function.getAttributeValue('FWHMs')
@@ -466,10 +526,46 @@ class CrystalFieldMultiSite(object):
 
     @FWHMs.setter
     def FWHMs(self, value):
-        if len(value) == 1:
-            value = value[0]
+        if islistlike(value):
+            if len(value) != len(self.Temperatures):
+                value = [value[0]] * len(self.Temperatures)
+        else:
             value = [value] * len(self.Temperatures)
         self.function.setAttributeValue('FWHMs', value)
+        self._resolutionModel = None
+
+    @property
+    def FWHM(self):
+        return self.FWHMs
+
+    @FWHM.setter
+    def FWHM(self, value):
+        self.FWHMs = value
+
+    @property
+    def ResolutionModel(self):
+        return self._resolutionModel
+
+    @ResolutionModel.setter
+    def ResolutionModel(self, value):
+        from .function import ResolutionModel
+        if hasattr(value, 'model'):
+            self._resolutionModel = value
+        else:
+            self._resolutionModel = ResolutionModel(value)
+        nSpec = len(self.Temperatures)
+        if nSpec > 1:
+            if not self._resolutionModel.multi or self._resolutionModel.NumberOfSpectra != nSpec:
+                raise RuntimeError('Resolution model is expected to have %s functions, found %s' %
+                                   (nSpec, self._resolutionModel.NumberOfSpectra))
+            for i in range(nSpec):
+                model = self._resolutionModel.model[i]
+                self.function.setAttributeValue('sp%i.FWHMX' % i, model[0])
+                self.function.setAttributeValue('sp%i.FWHMY' % i, model[1])
+        else:
+            model = self._resolutionModel.model
+            self.function.setAttributeValue('FWHMX', model[0])
+            self.function.setAttributeValue('FWHMY', model[1])
 
     @property
     def FWHMVariation(self):
diff --git a/scripts/Inelastic/CrystalField/__init__.py b/scripts/Inelastic/CrystalField/__init__.py
index 17cf614835b81aeb3b6b1c7d4b8bafe7bc7a5449..20fd13f37bb8abddefd4308b4d230dd7d7264fc4 100644
--- a/scripts/Inelastic/CrystalField/__init__.py
+++ b/scripts/Inelastic/CrystalField/__init__.py
@@ -1,6 +1,7 @@
 from __future__ import (absolute_import, division, print_function)
-from .fitting import CrystalField, CrystalFieldFit, CrystalFieldMulti
+from .fitting import CrystalField, CrystalFieldFit
 from .function import PeaksFunction, Background, Function, ResolutionModel, PhysicalProperties
 from .pointcharge import PointCharge
-__all__ = ['CrystalField', 'CrystalFieldFit', 'CrystalFieldMulti', 'PeaksFunction',
+from .CrystalFieldMultiSite import CrystalFieldMultiSite
+__all__ = ['CrystalField', 'CrystalFieldFit', 'CrystalFieldMultiSite', 'PeaksFunction',
            'Background', 'Function', 'ResolutionModel', 'PhysicalProperties', 'PointCharge']
diff --git a/scripts/Inelastic/CrystalField/fitting.py b/scripts/Inelastic/CrystalField/fitting.py
index aab99ebca585ddecb1347b76fc8a463a861b26af..f07526d4cfd5b9ca3a1a6fed0a54e474269757c4 100644
--- a/scripts/Inelastic/CrystalField/fitting.py
+++ b/scripts/Inelastic/CrystalField/fitting.py
@@ -2,7 +2,7 @@ from __future__ import (absolute_import, division, print_function)
 import numpy as np
 import re
 import warnings
-from six import string_types
+from six import string_types, iteritems
 
 
 # RegEx pattern matching a composite function parameter name, eg f2.Sigma.
@@ -26,20 +26,68 @@ def makeWorkspace(xArray, yArray):
 
 
 def islistlike(arg):
-    return (not hasattr(arg, "strip")) and (hasattr(arg, "__getitem__") or hasattr(arg, "__iter__"))
+    return (not hasattr(arg, "strip")) and (hasattr(arg, "__getitem__") or hasattr(arg, "__iter__")) and hasattr(arg, "__len__")
+
+
+def ionname2Nre(ionname):
+    ion_nre_map = {'Ce': 1, 'Pr': 2, 'Nd': 3, 'Pm': 4, 'Sm': 5, 'Eu': 6, 'Gd': 7,
+                   'Tb': 8, 'Dy': 9, 'Ho': 10, 'Er': 11, 'Tm': 12, 'Yb': 13}
+    if ionname not in ion_nre_map.keys():
+        msg = 'Value %s is not allowed for attribute Ion.\nList of allowed values: %s' % \
+              (ionname, ', '.join(list(ion_nre_map.keys())))
+        arbitraryJ = re.match('[SJsj]([0-9\.]+)', ionname)
+        if arbitraryJ and (float(arbitraryJ.group(1)) % 0.5) == 0:
+            nre = int(-float(arbitraryJ.group(1)) * 2.)
+            if nre < -99:
+                raise RuntimeError('J value ' + str(-nre / 2) + ' is too large.')
+        else:
+            raise RuntimeError(msg+', S<n>, J<n>')
+    else:
+        nre = ion_nre_map[ionname]
+    return nre
+
+
+def cfpstrmaker(x, pref='B'):
+    return [pref+str(k)+str(x) for k in [2, 4, 6] if x <= k]
+
+
+def getSymmAllowedParam(sym_str):
+    if 'T' in sym_str or 'O' in sym_str:
+        return ['B40', 'B44', 'B60', 'B64']
+    if any([sym_str == val for val in ['C1', 'Ci']]):
+        return sum([cfpstrmaker(i) for i in range(7)] +
+                   [cfpstrmaker(i, 'IB') for i in range(1, 7)],[])
+    retval = cfpstrmaker(0)
+    if '6' in sym_str or '3' in sym_str:
+        retval += cfpstrmaker(6)
+        if any([sym_str == val for val in ['C6', 'C3h', 'C6h']]):
+            retval += cfpstrmaker(6, 'IB')
+    if ('3' in sym_str and '3h' not in sym_str) or 'S6' in sym_str:
+        retval += cfpstrmaker(3)
+        if any([sym_str == val for val in ['C3', 'S6']]):
+            retval += cfpstrmaker(3, 'IB') + cfpstrmaker(6, 'IB')
+    if '4' in sym_str or '2' in sym_str:
+        retval += cfpstrmaker(4)
+        if any([sym_str == val for val in ['C4', 'S4', 'C4h']]):
+            retval += cfpstrmaker(4, 'IB')
+    if ('2' in sym_str and '2d' not in sym_str) or 'Cs' in sym_str:
+        retval += cfpstrmaker(2)
+        if any([sym_str == val for val in ['C2', 'Cs', 'C2h']]):
+            retval += cfpstrmaker(2, 'IB') + cfpstrmaker(4, 'IB')
+    return retval
 
 
 #pylint: disable=too-many-instance-attributes,too-many-public-methods
 class CrystalField(object):
     """Calculates the crystal fields for one ion"""
 
-    ion_nre_map = {'Ce': 1, 'Pr': 2, 'Nd': 3, 'Pm': 4, 'Sm': 5, 'Eu': 6, 'Gd': 7,
-                   'Tb': 8, 'Dy': 9, 'Ho': 10, 'Er': 11, 'Tm': 12, 'Yb': 13}
-
     allowed_symmetries = ['C1', 'Ci', 'C2', 'Cs', 'C2h', 'C2v', 'D2', 'D2h', 'C4', 'S4', 'C4h',
                           'D4', 'C4v', 'D2d', 'D4h', 'C3', 'S6', 'D3', 'C3v', 'D3d', 'C6', 'C3h',
                           'C6h', 'D6', 'C6v', 'D3h', 'D6h', 'T', 'Td', 'Th', 'O', 'Oh']
 
+    lande_g = [6.0 / 7., 4.0 / 5., 8.0 / 11., 3.0 / 5., 2.0 / 7., 0.0, 2.0,
+               3.0 / 2., 4.0 / 3., 5.0 / 4.,  6.0 / 5., 7.0 / 6., 8.0 / 7.]
+
     default_peakShape = 'Gaussian'
     default_background = 'FlatBackground'
     default_spectrum_size = 200
@@ -132,6 +180,12 @@ class CrystalField(object):
 
         free_parameters = {key: kwargs[key] for key in kwargs if key in CrystalField.field_parameter_names}
 
+        if 'ResolutionModel' in kwargs and 'FWHM' in kwargs:
+            msg = 'Both ''ResolutionModel'' and ''FWHM'' specified but can only accept one width option.'
+            msg += ' Prefering to use ResolutionModel, and ignoring FWHM.'
+            kwargs.pop('FWHM')
+            warnings.warn(msg, SyntaxWarning)
+
         for key in kwargs:
             if key == 'ToleranceEnergy':
                 self.ToleranceEnergy = kwargs[key]
@@ -157,7 +211,7 @@ class CrystalField(object):
         for param in CrystalField.field_parameter_names:
             if param in free_parameters:
                 self.function.setParameter(param, free_parameters[param])
-            else:
+            elif param not in getSymmAllowedParam(self.Symmetry):
                 self.function.fixParameter(param)
 
         self._setPeaks()
@@ -173,8 +227,6 @@ class CrystalField(object):
         self._peakList = None
 
         # Spectra
-        self._dirty_spectra = True
-        self._spectra = {}
         self._plot_window = {}
 
         # self._setDefaultTies()
@@ -248,6 +300,7 @@ class CrystalField(object):
         """
         if hasattr(i, 'toString'):
             out = i.toString()
+            ppobj = i
         else:
             if self._physprop is None:
                 raise RuntimeError('Physical properties environment not defined.')
@@ -261,6 +314,11 @@ class CrystalField(object):
         if len(fieldParams) > 0:
             out += ',%s' % ','.join(['%s=%s' % item for item in fieldParams.items()])
         ties = self._getFieldTies()
+        from .function import PhysicalProperties
+        if ppobj.TypeID == PhysicalProperties.SUSCEPTIBILITY:
+            ties += ',' if ties else ''
+            ties += 'Lambda=0' if ppobj.Lambda == 0. else ''
+            ties += ',Chi0=0' if ppobj.Chi0 == 0. else ''
         if len(ties) > 0:
             out += ',ties=(%s)' % ties
         constraints = self._getFieldConstraints()
@@ -269,7 +327,6 @@ class CrystalField(object):
         return out
 
     def makeMultiSpectrumFunction(self):
-        import re
         return re.sub(r'FWHM[X|Y]\d+=\(\),', '', str(self.function))
 
     @property
@@ -290,14 +347,10 @@ class CrystalField(object):
         ...
         cf.Ion = 'Pr'
         """
-        if value not in self.ion_nre_map.keys():
-            msg = 'Value %s is not allowed for attribute Ion.\nList of allowed values: %s' % \
-                  (value, ', '.join(list(self.ion_nre_map.keys())))
-            raise RuntimeError(msg)
+        self._nre = ionname2Nre(value)
         self.crystalFieldFunction.setAttributeValue('Ion', value)
         self._dirty_eigensystem = True
         self._dirty_peaks = True
-        self._dirty_spectra = True
 
     @property
     def Symmetry(self):
@@ -324,7 +377,6 @@ class CrystalField(object):
         self.crystalFieldFunction.setAttributeValue('Symmetry', value)
         self._dirty_eigensystem = True
         self._dirty_peaks = True
-        self._dirty_spectra = True
 
     @property
     def ToleranceEnergy(self):
@@ -336,7 +388,6 @@ class CrystalField(object):
         """Set energy tolerance"""
         self.crystalFieldFunction.setAttributeValue('ToleranceEnergy', float(value))
         self._dirty_peaks = True
-        self._dirty_spectra = True
 
     @property
     def ToleranceIntensity(self):
@@ -348,7 +399,6 @@ class CrystalField(object):
         """Set intensity tolerance"""
         self.crystalFieldFunction.setAttributeValue('ToleranceIntensity', float(value))
         self._dirty_peaks = True
-        self._dirty_spectra = True
 
     @property
     def IntensityScaling(self):
@@ -378,7 +428,6 @@ class CrystalField(object):
                 self.crystalFieldFunction.setParameter(paramName, value[i])
 
         self._dirty_peaks = True
-        self._dirty_spectra = True
 
     @property
     def Temperature(self):
@@ -401,7 +450,6 @@ class CrystalField(object):
                 return
             self.crystalFieldFunction.setAttributeValue('Temperature', float(value))
         self._dirty_peaks = True
-        self._dirty_spectra = True
 
     @property
     def FWHM(self):
@@ -420,12 +468,30 @@ class CrystalField(object):
         if self._isMultiSpectrum:
             if not islistlike(value):
                 value = [value] * self.NumberOfSpectra
+            if len(value) != len(self.Temperature):
+                if self.PhysicalProperty is not None and len(value) == len(self.Temperature) - len(self.PhysicalProperty):
+                    value = value + [0] * len(self.PhysicalProperty)
+                else:
+                    raise RuntimeError('Vector of FWHMs must either have same size as '
+                                       'Temperatures (%i) or have size 1.' % (len(self.Temperature)))
             self.crystalFieldFunction.setAttributeValue('FWHMs', value)
         else:
             if islistlike(value):
                 raise ValueError('FWHM is expected to be a single floating point value')
             self.crystalFieldFunction.setAttributeValue('FWHM', float(value))
-        self._dirty_spectra = True
+        # If both FWHM and ResolutionModel is set, may cause runtime errors
+        self._resolutionModel = None
+        if self._isMultiSpectrum:
+            for i in range(self.NumberOfSpectra):
+                if self.crystalFieldFunction.getAttributeValue('FWHMX%s' % i):
+                    self.crystalFieldFunction.setAttributeValue('FWHMX%s' % i, [])
+                if self.crystalFieldFunction.getAttributeValue('FWHMY%s' % i):
+                    self.crystalFieldFunction.setAttributeValue('FWHMY%s' % i, [])
+        else:
+            if self.crystalFieldFunction.getAttributeValue('FWHMX'):
+                self.crystalFieldFunction.setAttributeValue('FWHMX', [])
+            if self.crystalFieldFunction.getAttributeValue('FWHMY'):
+                self.crystalFieldFunction.setAttributeValue('FWHMY', [])
 
     @property
     def FWHMVariation(self):
@@ -434,13 +500,11 @@ class CrystalField(object):
     @FWHMVariation.setter
     def FWHMVariation(self, value):
         self.crystalFieldFunction.setAttributeValue('FWHMVariation', float(value))
-        self._dirty_spectra = True
 
     def __getitem__(self, item):
         return self.crystalFieldFunction.getParameterValue(item)
 
     def __setitem__(self, key, value):
-        self._dirty_spectra = True
         self.crystalFieldFunction.setParameter(key, value)
 
     @property
@@ -450,7 +514,7 @@ class CrystalField(object):
     @ResolutionModel.setter
     def ResolutionModel(self, value):
         from .function import ResolutionModel
-        if isinstance(value, ResolutionModel):
+        if hasattr(value, 'model'):
             self._resolutionModel = value
         else:
             self._resolutionModel = ResolutionModel(value)
@@ -466,6 +530,11 @@ class CrystalField(object):
             model = self._resolutionModel.model
             self.crystalFieldFunction.setAttributeValue('FWHMX', model[0])
             self.crystalFieldFunction.setAttributeValue('FWHMY', model[1])
+        # If FWHM is set, it overrides resolution model, so unset it
+        if self._isMultiSpectrum and any(self.crystalFieldFunction.getAttributeValue('FWHMs')):
+            self.crystalFieldFunction.setAttributeValue('FWHMs', [0.] * self.NumberOfSpectra)
+        elif not self._isMultiSpectrum and self.crystalFieldFunction.getAttributeValue('FWHM'):
+            self.crystalFieldFunction.setAttributeValue('FWHM', 0.)
 
     @property
     def FixAllPeaks(self):
@@ -511,11 +580,10 @@ class CrystalField(object):
             value: an instance of function.Background class or a list of instances
                 in a multi-spectrum case
         """
-        from .function import Background
         from mantid.simpleapi import FunctionFactory
         if self._background is not None:
             raise ValueError('Background has been set already')
-        if not isinstance(value, Background):
+        if not hasattr(value, 'toString'):
             raise TypeError('Expected a Background object, found %s' % str(value))
         if not self._isMultiSpectrum:
             fun_str = value.toString() + ';' + str(self.function)
@@ -548,9 +616,8 @@ class CrystalField(object):
 
     @PhysicalProperty.setter
     def PhysicalProperty(self, value):
-        from .function import PhysicalProperties
         vlist = value if islistlike(value) else [value]
-        if all([isinstance(pp, PhysicalProperties) for pp in vlist]):
+        if all([hasattr(pp, 'TypeID') for pp in vlist]):
             nOldPP = len(self._physprop) if islistlike(self._physprop) else (0 if self._physprop is None else 1)
             self._physprop = value
         else:
@@ -576,7 +643,14 @@ class CrystalField(object):
             self.function.setAttributeValue('PhysicalProperties', [0]*len(tt)+ppids)
             for attribs in [pp.getAttributes(i+len(tt)) for i, pp in enumerate(vlist)]:
                 for item in attribs.items():
-                    self.function.setAttributeValue(item[0], item[1])
+                    if 'Lambda' in item[0] or 'Chi0' in item[0]:
+                        self.function.setParameter(item[0], item[1])
+                        if item[1] == 0.:
+                            self.function.tie(item[0], '0.')
+                        else:
+                            self.function.removeTie(item[0])
+                    else:
+                        self.function.setAttributeValue(item[0], item[1])
 
     @property
     def isPhysicalPropertyOnly(self):
@@ -642,10 +716,6 @@ class CrystalField(object):
         @param ws_index:  An index of a spectrum from workspace to use.
         @return: A tuple of (x, y) arrays
         """
-        if self._dirty_spectra:
-            self._spectra = {}
-            self._dirty_spectra = False
-
         wksp = workspace
         # Allow to call getSpectrum with a workspace as the first argument.
         if not isinstance(i, int):
@@ -668,16 +738,12 @@ class CrystalField(object):
             return self._calcSpectrum(i, wksp, ws_index)
 
         if xArray is None:
-            if i in self._spectra:
-                return self._spectra[i]
-            else:
-                x_min, x_max = self.calc_xmin_xmax(i)
-                xArray = np.linspace(x_min, x_max, self.default_spectrum_size)
+            x_min, x_max = self.calc_xmin_xmax(i)
+            xArray = np.linspace(x_min, x_max, self.default_spectrum_size)
 
         yArray = np.zeros_like(xArray)
         wksp = makeWorkspace(xArray, yArray)
-        self._spectra[i] = self._calcSpectrum(i, wksp, 0)
-        return self._spectra[i]
+        return self._calcSpectrum(i, wksp, 0)
 
     def getHeatCapacity(self, workspace=None, ws_index=0):
         """
@@ -822,6 +888,22 @@ class CrystalField(object):
 
         return self._getPhysProp(PhysicalProperties(pptype, *args, **kwargs), workspace, ws_index)
 
+    def getDipoleMatrix(self):
+        """Returns the dipole transition matrix as a numpy array"""
+        from scipy.constants import physical_constants
+        from CrystalField.energies import energies
+        self._calcEigensystem()
+        _, _, hx = energies(self._nre, BextX=1.0)
+        _, _, hy = energies(self._nre, BextY=1.0)
+        _, _, hz = energies(self._nre, BextZ=1.0)
+        ix = np.dot(np.conj(np.transpose(self._eigenvectors)), np.dot(hx, self._eigenvectors))
+        iy = np.dot(np.conj(np.transpose(self._eigenvectors)), np.dot(hy, self._eigenvectors))
+        iz = np.dot(np.conj(np.transpose(self._eigenvectors)), np.dot(hz, self._eigenvectors))
+        gj = 2. if (self._nre < 1) else self.lande_g[self._nre - 1]
+        gJuB = gj * physical_constants['Bohr magneton in eV/T'][0] * 1000.
+        trans = np.multiply(ix, np.conj(ix)) + np.multiply(iy, np.conj(iy)) + np.multiply(iz, np.conj(iz))
+        return trans / (gJuB ** 2)
+
     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
@@ -898,12 +980,32 @@ class CrystalField(object):
             symmetries = [self.Symmetry, other.Symmetry]
             params = {}
             temperatures = [self._getTemperature(x) for x in range(self.NumberOfSpectra)]
-            fwhms = [self._getFWHM(x) for x in range(self.NumberOfSpectra)]
             for bparam in CrystalField.field_parameter_names:
                 params['ion0.' + bparam] = self[bparam]
                 params['ion1.' + bparam] = other[bparam]
-            return CrystalFieldMultiSite(Ions=ions, Symmetries=symmetries, Temperatures=temperatures,
-                                         FWHMs = fwhms, parameters=params, abundances=[1.0, 1.0])
+            ties = {}
+            fixes = []
+            for prefix, obj in iteritems({'ion0.':self, 'ion1.':other}):
+                tiestr = obj.function.getTies()
+                if tiestr:
+                    for tiepair in [tie.split('=') for tie in tiestr.split(',')]:
+                        ties[prefix + tiepair[0]] = tiepair[1]
+                for par_id in [id for id in range(obj.function.nParams()) if obj.function.isFixed(id)]:
+                    parName = obj.function.getParamName(par_id)
+                    if obj.background is not None:
+                        parName = parName.split('.')[-1]
+                    if parName not in self.field_parameter_names:
+                        continue
+                    fixes.append(prefix + parName)
+            if self._resolutionModel is None:
+                fwhms = [self._getFWHM(x) for x in range(self.NumberOfSpectra)]
+                return CrystalFieldMultiSite(Ions=ions, Symmetries=symmetries, Temperatures=temperatures,
+                                             FWHM = fwhms, parameters=params, abundances=[1.0, 1.0],
+                                             ties = ties, fixedParameters = fixes)
+            else:
+                return CrystalFieldMultiSite(Ions=ions, Symmetries=symmetries, Temperatures=temperatures,
+                                             ResolutionModel=self._resolutionModel, parameters=params,
+                                             abundances=[1.0, 1.0], ties = ties, fixedParameters = fixes)
 
     def __mul__(self, factor):
         ffactor = float(factor)
@@ -970,13 +1072,11 @@ class CrystalField(object):
         return params
 
     def _getFieldTies(self):
-        import re
-        ties = re.match('ties=\((.*?)\)', str(self.crystalFieldFunction))
+        ties = re.search('ties=\((.*?)\)', str(self.crystalFieldFunction))
         return ties.group(1) if ties else ''
 
     def _getFieldConstraints(self):
-        import re
-        constraints = re.match('constraints=\((.*?)\)', str(self.crystalFieldFunction))
+        constraints = re.search('constraints=\((.*?)\)', str(self.crystalFieldFunction))
         return constraints.group(1) if constraints else ''
 
     def _getPhysProp(self, ppobj, workspace, ws_index):
@@ -1012,9 +1112,8 @@ class CrystalField(object):
         """
         if self._dirty_eigensystem:
             import CrystalField.energies as energies
-            nre = self.ion_nre_map[self.Ion]
             self._eigenvalues, self._eigenvectors, self._hamiltonian = \
-                energies.energies(nre, **self._getFieldParameters())
+                energies.energies(self._nre, **self._getFieldParameters())
             self._dirty_eigensystem = False
 
     def _calcPeaksList(self, i):
@@ -1066,6 +1165,7 @@ class CrystalFieldSite(object):
 
     def __add__(self, other):
         from CrystalField.CrystalFieldMultiSite import CrystalFieldMultiSite
+        from CrystalField import CrystalField
         if isinstance(other, CrystalField):
             abundances = [self.abundance, 1.0]
         elif isinstance(other, CrystalFieldSite):
@@ -1079,233 +1179,18 @@ class CrystalFieldSite(object):
         ions = [self.crystalField.Ion, other.Ion]
         symmetries = [self.crystalField.Symmetry, other.Symmetry]
         temperatures = [self.crystalField._getTemperature(x) for x in range(self.crystalField.NumberOfSpectra)]
-        FWHMs = [self.crystalField._getFWHM(x) for x in range(self.crystalField.NumberOfSpectra)]
         params = {}
         for bparam in CrystalField.field_parameter_names:
             params['ion0.' + bparam] = self.crystalField[bparam]
             params['ion1.' + bparam] = other[bparam]
-        return CrystalFieldMultiSite(Ions=ions, Symmetries=symmetries, Temperatures=temperatures, FWHMs = FWHMs,
-                                     abundances=abundances, parameters=params)
-
-
-class CrystalFieldMulti(object):
-    """CrystalFieldMulti represents crystal field of multiple ions."""
-
-    def __init__(self, *args):
-        self.sites = []
-        self.abundances = []
-        for arg in args:
-            if isinstance(arg, CrystalField):
-                self.sites.append(arg)
-                self.abundances.append(1.0)
-            elif isinstance(arg, CrystalFieldSite):
-                self.sites.append(arg.crystalField)
-                self.abundances.append(arg.abundance)
-            else:
-                raise RuntimeError('Cannot include an object of type %s into a CrystalFieldMulti' % type(arg))
-        self._ties = {}
-
-    def makeSpectrumFunction(self):
-        fun = ';'.join([a.makeSpectrumFunction() for a in self.sites])
-        fun += self._makeIntensityScalingTies()
-        ties = self.getTies()
-        if len(ties) > 0:
-            fun += ';ties=(%s)' % ties
-        return 'composite=CompositeFunction,NumDeriv=1;' + fun
-
-    def makePhysicalPropertiesFunction(self):
-        # Handles relative intensities. Scaling factors here a fixed attributes not
-        # variable parameters and we require the sum to be unity.
-        factors = np.array(self.abundances)
-        sum_factors = np.sum(factors)
-        factstr = [',ScaleFactor=%s' % (str(factors[i] / sum_factors)) for i in range(len(self.sites))]
-        fun = ';'.join([a.makePhysicalPropertiesFunction()+factstr[i] for a,i in enumerate(self.sites)])
-        ties = self.getTies()
-        if len(ties) > 0:
-            fun += ';ties=(%s)' % ties
-        return fun
-
-    def makeMultiSpectrumFunction(self):
-        fun = ';'.join([a.makeMultiSpectrumFunction() for a in self.sites])
-        fun += self._makeIntensityScalingTiesMulti()
-        ties = self.getTies()
-        if len(ties) > 0:
-            fun += ';ties=(%s)' % ties
-        return 'composite=CompositeFunction,NumDeriv=1;' + fun
-
-    def ties(self, **kwargs):
-        """Set ties on the parameters."""
-        for tie in kwargs:
-            self._ties[tie] = kwargs[tie]
-
-    def getTies(self):
-        ties = ['%s=%s' % item for item in self._ties.items()]
-        return ','.join(ties)
-
-    def getSpectrum(self, i=0, workspace=None, ws_index=0):
-        tt = []
-        for site in self.sites:
-            tt = tt + (list(site.Temperature) if islistlike(site.Temperature) else [site.Temperature])
-        if any([val < 0 for val in tt]):
-            raise RuntimeError('You must first define a temperature for the spectrum')
-        largest_abundance= max(self.abundances)
-        if workspace is not None:
-            xArray, yArray = self.sites[0].getSpectrum(i, workspace, ws_index)
-            yArray *= self.abundances[0] / largest_abundance
-            ia = 1
-            for arg in self.sites[1:]:
-                _, yyArray = arg.getSpectrum(i, workspace, ws_index)
-                yArray += yyArray * self.abundances[ia] / largest_abundance
-                ia += 1
-            return xArray, yArray
-        x_min = 0.0
-        x_max = 0.0
-        for arg in self.sites:
-            xmin, xmax = arg.calc_xmin_xmax(i)
-            if xmin < x_min:
-                x_min = xmin
-            if xmax > x_max:
-                x_max = xmax
-        xArray = np.linspace(x_min, x_max, CrystalField.default_spectrum_size)
-        _, yArray = self.sites[0].getSpectrum(i, xArray, ws_index)
-        yArray *= self.abundances[0] / largest_abundance
-        ia = 1
-        for arg in self.sites[1:]:
-            _, yyArray = arg.getSpectrum(i, xArray, ws_index)
-            yArray += yyArray * self.abundances[ia] / largest_abundance
-            ia += 1
-        return xArray, yArray
-
-    def update(self, func):
-        nFunc = func.nFunctions()
-        assert nFunc == len(self.sites)
-        for i in range(nFunc):
-            self.sites[i].update(func[i])
-
-    def update_multi(self, func):
-        nFunc = func.nFunctions()
-        assert nFunc == len(self.sites)
-        for i in range(nFunc):
-            self.sites[i].update_multi(func[i])
-
-    def _makeIntensityScalingTies(self):
-        """
-        Make a tie string that ties IntensityScaling's of the sites according to their abundances.
-        """
-        n_sites = len(self.sites)
-        if n_sites < 2:
-            return ''
-        factors = np.array(self.abundances)
-        i_largest = np.argmax(factors)
-        largest_factor = factors[i_largest]
-        tie_template = 'f%s.IntensityScaling=%s*' + 'f%s.IntensityScaling' % i_largest
-        ties = []
-        for i in range(n_sites):
-            if i != i_largest:
-                ties.append(tie_template % (i, factors[i] / largest_factor))
-        s = ';ties=(%s)' % ','.join(ties)
-        return s
-
-    def _makeIntensityScalingTiesMulti(self):
-        """
-        Make a tie string that ties IntensityScaling's of the sites according to their abundances.
-        """
-        n_sites = len(self.sites)
-        if n_sites < 2:
-            return ''
-        factors = np.array(self.abundances)
-        i_largest = np.argmax(factors)
-        largest_factor = factors[i_largest]
-        tie_template = 'f{1}.IntensityScaling{0}={2}*f%s.IntensityScaling{0}' % i_largest
-        ties = []
-        n_spectra = self.sites[0].NumberOfSpectra
-        for spec in range(n_spectra):
-            for i in range(n_sites):
-                if i != i_largest:
-                    ties.append(tie_template.format(spec, i, factors[i] / largest_factor))
-        s = ';ties=(%s)' % ','.join(ties)
-        return s
-
-    @property
-    def isPhysicalPropertyOnly(self):
-        return all([a.isPhysicalPropertyOnly for a in self.sites])
-
-    @property
-    def PhysicalProperty(self):
-        return [a.PhysicalProperty for a in self.sites]
-
-    @PhysicalProperty.setter
-    def PhysicalProperty(self, value):
-        for a in self.sites:
-            a.PhysicalProperty = value
-
-    @property
-    def NumberOfSpectra(self):
-        """ Returns the number of expected workspaces """
-        num_spec = []
-        for site in self.sites:
-            num_spec.append(site.NumberOfSpectra)
-        if len(set(num_spec)) > 1:
-            raise ValueError('Number of spectra for each site not consistent with each other')
-        return num_spec[0]
-
-    @property
-    def Temperature(self):
-        tt = []
-        for site in self.sites:
-            tt.append([val for val in (site.Temperature if islistlike(site.Temperature) else [site.Temperature])])
-        if len(set([tuple(val) for val in tt])) > 1:
-            raise ValueError('Temperatures of spectra for each site not consistent with each other')
-        return tt[0]
-
-    @Temperature.setter
-    def Temperature(self, value):
-        for site in self.sites:
-            site.Temperature = value
-
-    def __add__(self, other):
-        if isinstance(other, CrystalFieldMulti):
-            cfm = CrystalFieldMulti()
-            cfm.sites += self.sites + other.sites
-            cfm.abundances += self.abundances + other.abundances
-            return cfm
-        elif isinstance(other, CrystalField):
-            cfm = CrystalFieldMulti()
-            cfm.sites += self.sites + [other]
-            cfm.abundances += self.abundances + [1]
-            return cfm
-        elif isinstance(other, CrystalFieldSite):
-            cfm = CrystalFieldMulti()
-            cfm.sites += self.sites + [other.crystalField]
-            cfm.abundances += self.abundances + [other.abundance]
-            return cfm
+        if self.crystalField.ResolutionModel is None:
+            FWHM = [self.crystalField._getFWHM(x) for x in range(self.crystalField.NumberOfSpectra)]
+            return CrystalFieldMultiSite(Ions=ions, Symmetries=symmetries, Temperatures=temperatures, FWHM = FWHM,
+                                         abundances=abundances, parameters=params)
         else:
-            raise TypeError('Cannot add %s to CrystalFieldMulti' % other.__class__.__name__)
-
-    def __radd__(self, other):
-        if isinstance(other, CrystalFieldMulti):
-            cfm = CrystalFieldMulti()
-            cfm.sites += other.sites + self.sites
-            cfm.abundances += other.abundances + self.abundances
-            return cfm
-        elif isinstance(other, CrystalField):
-            cfm = CrystalFieldMulti()
-            cfm.sites += [other] + self.sites
-            cfm.abundances += [1] + self.abundances
-            return cfm
-        elif isinstance(other, CrystalFieldSite):
-            cfm = CrystalFieldMulti()
-            cfm.sites += [other.crystalField] + self.sites
-            cfm.abundances += [other.abundance] + self.abundances
-            return cfm
-        else:
-            raise TypeError('Cannot add %s to CrystalFieldMulti' % other.__class__.__name__)
-
-    def __len__(self):
-        return len(self.sites)
-
-    def __getitem__(self, item):
-        return self.sites[item]
+            return CrystalFieldMultiSite(Ions=ions, Symmetries=symmetries, Temperatures=temperatures,
+                                         ResolutionModel=self.crystalField.ResolutionModel,
+                                         abundances=abundances, parameters=params)
 
 
 #pylint: disable=too-few-public-methods
@@ -1314,12 +1199,15 @@ class CrystalFieldFit(object):
     Object that controls fitting.
     """
 
-    def __init__(self, Model=None, Temperature=None, FWHM=None, InputWorkspace=None, **kwargs):
+    def __init__(self, Model=None, Temperature=None, FWHM=None, InputWorkspace=None,
+                 ResolutionModel=None, **kwargs):
         self.model = Model
         if Temperature is not None:
             self.model.Temperature = Temperature
         if FWHM is not None:
             self.model.FWHM = FWHM
+        if ResolutionModel is not None:
+            self.model.ResolutionModel = ResolutionModel
         self._input_workspace = InputWorkspace
         self._output_workspace_base_name = 'fit'
         self._fit_properties = kwargs
@@ -1347,11 +1235,22 @@ class CrystalFieldFit(object):
 
     def estimate_parameters(self, EnergySplitting, Parameters, **kwargs):
         from CrystalField.normalisation import split2range
+        from CrystalField.CrystalFieldMultiSite import CrystalFieldMultiSite
         from mantid.api import mtd
         self.check_consistency()
-        ranges = split2range(Ion=self.model.Ion, EnergySplitting=EnergySplitting,
-                             Parameters=Parameters)
-        constraints = [('%s<%s<%s' % (-bound, parName, bound)) for parName, bound in ranges.items()]
+        if isinstance(self.model, CrystalFieldMultiSite):
+            constraints = []
+            for ni in range(len(self.model.Ions)):
+                pars = Parameters[ni] if islistlike(Parameters[ni]) else Parameters
+                ion = self.model.Ions[ni]
+                ranges = split2range(Ion=ion, EnergySplitting=EnergySplitting, Parameters=pars)
+                constraints += [('%s<ion%d.%s<%s' % (-bound, ni, parName, bound))
+                                for parName, bound in ranges.items()]
+        else:
+            ranges = split2range(Ion=self.model.Ion, EnergySplitting=EnergySplitting,
+                                 Parameters=Parameters)
+            constraints = [('%s<%s<%s' % (-bound, parName, bound))
+                           for parName, bound in ranges.items()]
         self.model.constraints(*constraints)
         if 'Type' not in kwargs or kwargs['Type'] == 'Monte Carlo':
             if 'OutputWorkspace' in kwargs and kwargs['OutputWorkspace'].strip() != '':
@@ -1378,12 +1277,14 @@ class CrystalFieldFit(object):
         ne = self.get_number_estimates()
         if ne == 0:
             raise RuntimeError('There are no estimated parameters.')
-        if index >= ne:
+        if index > ne:
             raise RuntimeError('There are only %s sets of estimated parameters, requested set #%s' % (ne, index))
+        from CrystalField.CrystalFieldMultiSite import CrystalFieldMultiSite
         for row in range(self._estimated_parameters.rowCount()):
             name = self._estimated_parameters.cell(row, 0)
             value = self._estimated_parameters.cell(row, index)
-            self.model[name] = value
+            model_pname = name if isinstance(self.model, CrystalFieldMultiSite) else name.split('.')[-1]
+            self.model[model_pname] = value
             if self._function is not None:
                 self._function.setParameter(name, value)
 
diff --git a/scripts/Inelastic/CrystalField/function.py b/scripts/Inelastic/CrystalField/function.py
index 5d6f24e20778cd609b540287efacb76ca709709d..c92547e806ad2d4cbd3b5a842cb8fe38368c2121 100644
--- a/scripts/Inelastic/CrystalField/function.py
+++ b/scripts/Inelastic/CrystalField/function.py
@@ -433,13 +433,18 @@ class ResolutionModel:
             x = self._mergeArrays(x, xx)
             y = self._mergeArrays(y, yy)
             n = len(x)
-        return x, y
+        return list(x), list(y)
 
 
 class PhysicalProperties(object):
     """
     Contains information about measurement conditions of physical properties
     """
+    HEATCAPACITY = 1
+    SUSCEPTIBILITY = 2
+    MAGNETISATION = 3
+    MAGNETICMOMENT = 4
+
     def _str2id(self, typeid):
         mappings = [['cp', 'cv', 'heatcap'], ['chi', 'susc'], ['mag', 'm(h)'], ['mom', 'm(t)']]
         for id in range(4):
@@ -461,19 +466,21 @@ class PhysicalProperties(object):
         :param temperature: the temperature in Kelvin of measurements of M(H)
         :param inverse: a boolean indicating whether susceptibility is chi or 1/chi or M(T) or 1/M(T)
         :param unit: the unit the data was measured in. Either: 'bohr', 'SI' or 'cgs'.
+        :param lambda: (susceptibility only) the value of the exchange constant in inverse susc units
+        :param chi0: (susceptibility only) the value of the residual (background) susceptibility
 
         typeid is required in all cases, and all other parameters may be specified as keyword arguments.
         otherwise the syntax is:
 
         PhysicalProperties('Cp')  # No further parameters required for heat capacity
-        PhysicalProperties('chi', hdir, inverse, unit)
+        PhysicalProperties('chi', hdir, inverse, unit, lambda, chi0)
         PhysicalProperties('chi', unit)
         PhysicalProperties('mag', hdir, temp, unit)
         PhysicalProperties('mag', unit)
         PhysicalProperties('M(T)', hmag, hdir, inverse, unit)
         PhysicalProperties('M(T)', unit)
 
-        Defaults are: hdir=[0, 0, 1]; hmag=1; temp=1; inverse=False; unit='cgs'.
+        Defaults are: hdir=[0, 0, 1]; hmag=1; temp=1; inverse=False; unit='cgs'; lambda=chi0=0.
         """
         self._physpropUnit = 'cgs'
         self._suscInverseFlag = False
@@ -481,6 +488,7 @@ class PhysicalProperties(object):
         self._hmag = 1.
         self._physpropTemperature = 1.
         self._lambda = 0.    # Exchange parameter (for susceptibility only)
+        self._chi0 = 0.      # Residual/background susceptibility (for susceptibility only)
         self._typeid = self._str2id(typeid) if isinstance(typeid, string_types) else int(typeid)
         try:
             initialiser = getattr(self, 'init' + str(self._typeid))
@@ -532,11 +540,11 @@ class PhysicalProperties(object):
 
     @property
     def Inverse(self):
-        return self._suscInverseFlag if (self._typeid == 2 or self._typeid == 4) else None
+        return self._suscInverseFlag if (self._typeid == self.SUSCEPTIBILITY or self._typeid == self.MAGNETICMOMENT) else None
 
     @Inverse.setter
     def Inverse(self, value):
-        if (self._typeid == 2 or self._typeid == 4):
+        if (self._typeid == self.SUSCEPTIBILITY or self._typeid == self.MAGNETICMOMENT):
             if isinstance(value, string_types):
                 self._suscInverseFlag = value.lower() in ['true', 't', '1', 'yes', 'y']
             else:
@@ -546,40 +554,49 @@ class PhysicalProperties(object):
 
     @property
     def Hdir(self):
-        return self._hdir if (self._typeid > 1) else None
+        return self._hdir if (self._typeid != self.HEATCAPACITY) else None
 
     @Hdir.setter
     def Hdir(self, value):
-        if (self._typeid > 1):
+        if (self._typeid != self.HEATCAPACITY):
             self._hdir = self._checkhdir(value)
 
     @property
     def Hmag(self):
-        return self._hmag if (self._typeid == 4) else None
+        return self._hmag if (self._typeid == self.MAGNETICMOMENT) else None
 
     @Hmag.setter
     def Hmag(self, value):
-        if (self._typeid == 4):
+        if (self._typeid == self.MAGNETICMOMENT):
             self._hmag = float(value)
 
     @property
     def Temperature(self):
-        return self._physpropTemperature if (self._typeid == 3) else None
+        return self._physpropTemperature if (self._typeid == self.MAGNETISATION) else None
 
     @Temperature.setter
     def Temperature(self, value):
-        if (self._typeid == 3):
+        if (self._typeid == self.MAGNETISATION):
             self._physpropTemperature = float(value)
 
     @property
     def Lambda(self):
-        return self._lambda if (self._typeid == 2) else None
+        return self._lambda if (self._typeid == self.SUSCEPTIBILITY) else None
 
     @Lambda.setter
     def Lambda(self, value):
-        if (self._typeid == 2):
+        if (self._typeid == self.SUSCEPTIBILITY):
             self._lambda = float(value)
 
+    @property
+    def Chi0(self):
+        return self._chi0 if (self._typeid == self.SUSCEPTIBILITY) else None
+
+    @Chi0.setter
+    def Chi0(self, value):
+        if (self._typeid == self.SUSCEPTIBILITY):
+            self._chi0 = float(value)
+
     def init1(self, *args, **kwargs):
         """ Initialises environment for heat capacity data """
         if len(args) > 0:
@@ -602,7 +619,7 @@ class PhysicalProperties(object):
 
     def init2(self, *args, **kwargs):
         """ Initialises environment for susceptibility data """
-        mapping = ['Hdir', 'Inverse', 'Unit', 'Lambda']
+        mapping = ['Hdir', 'Inverse', 'Unit', 'Lambda', 'Chi0']
         self._parseargs(mapping, *args, **kwargs)
 
     def init3(self, *args, **kwargs):
@@ -620,35 +637,38 @@ class PhysicalProperties(object):
         types = ['CrystalFieldHeatCapacity', 'CrystalFieldSusceptibility',
                  'CrystalFieldMagnetisation', 'CrystalFieldMoment']
         out = 'name=%s' % (types[self._typeid - 1])
-        if self._typeid > 1:
+        if self._typeid != self.HEATCAPACITY:
             out += ',Unit=%s' % (self._physpropUnit)
             if 'powder' in self._hdir:
                 out += ',powder=1'
             else:
                 out += ',Hdir=(%s)' % (','.join([str(hh) for hh in self._hdir]))
-            if self._typeid == 3:  # magnetisation M(H)
+            if self._typeid == self.MAGNETISATION:
                 out += ',Temperature=%s' % (self._physpropTemperature)
             else:            # either susceptibility or M(T)
                 out += ',inverse=%s' % (1 if self._suscInverseFlag else 0)
-                out += (',Hmag=%s' % (self._hmag)) if self._typeid==3 else ''
-                if self._typeid == 2 and self._lambda != 0:
+                out += (',Hmag=%s' % (self._hmag)) if self._typeid == self.MAGNETISATION else ''
+                if self._typeid == self.SUSCEPTIBILITY and self._lambda != 0:
                     out += ',Lambda=%s' % (self._lambda)
+                if self._typeid == self.SUSCEPTIBILITY and self._chi0 != 0:
+                    out += ',Chi0=%s' % (self._chi0)
         return out
 
     def getAttributes(self, dataset=None):
         """Returns a dictionary of PhysicalProperties attributes for use with IFunction"""
         dataset = '' if dataset is None else str(dataset)
         out = {}
-        if self._typeid > 1:
+        if self._typeid != self.HEATCAPACITY:
             out['Unit%s' % (dataset)] = self._physpropUnit
             if 'powder' in self._hdir:
                 out['powder%s' % (dataset)] = 1
             else:
                 out['Hdir%s' % (dataset)] = [float(hh) for hh in self._hdir] # needs to be list
-            if self._typeid != 3:  # either susceptibility or M(T)
+            if self._typeid != self.MAGNETISATION:  # either susceptibility or M(T)
                 out['inverse%s' % (dataset)] = 1 if self._suscInverseFlag else 0
-                if self._typeid==3:
+                if self._typeid == self.MAGNETICMOMENT:
                     out['Hmag%s' % (dataset)] = self._hmag
-                if self._typeid == 2 and self._lambda != 0:
-                    out['Lambda%s=' % (dataset)] = self._lambda
+                if self._typeid == self.SUSCEPTIBILITY:
+                    out['Lambda%s' % (dataset)] = self._lambda
+                    out['Chi0%s' % (dataset)] = self._chi0
         return out
diff --git a/scripts/Inelastic/CrystalField/normalisation.py b/scripts/Inelastic/CrystalField/normalisation.py
index cd2f9178697fbb906f32440c10419028d70c6ef1..f1df0597b84e9211e7e6ebd6792d23513d495548 100644
--- a/scripts/Inelastic/CrystalField/normalisation.py
+++ b/scripts/Inelastic/CrystalField/normalisation.py
@@ -1,6 +1,7 @@
 from __future__ import (absolute_import, division, print_function)
 import numpy as np
 from CrystalField.energies import energies as CFEnergy
+from CrystalField.fitting import ionname2Nre
 from six import iteritems
 from six import string_types
 
@@ -9,13 +10,14 @@ def _get_normalisation(nre, bnames):
     """ Helper function to calculate the normalisation factor.
         Defined as: ||Blm|| = sum_{Jz,Jz'} |<Jz|Blm|Jz'>|^2 / (2J+1)
     """
-    J = [0, 5./2, 4, 9./2, 4, 5./2, 0, 7./2, 6, 15./2, 8, 15./2, 6, 7./2]
+    Jvals = [0, 5./2, 4, 9./2, 4, 5./2, 0, 7./2, 6, 15./2, 8, 15./2, 6, 7./2]
+    J = (-nre / 2.) if (nre < 0) else Jvals[nre]
     retval = {}
     for bname in bnames:
         bdict = {bname: 1}
         ee, vv, ham = CFEnergy(nre, **bdict)
         Omat = np.mat(ham)
-        norm = np.trace(np.real(Omat * np.conj(Omat))) / (2*J[nre]+1)
+        norm = np.trace(np.real(Omat * np.conj(Omat))) / (2*J+1)
         retval[bname] = np.sqrt(np.abs(norm)) * np.sign(norm)
     return retval
 
@@ -25,12 +27,11 @@ def _parse_args(**kwargs):
     """
     # Some definitions
     Blms = ['B20', 'B21', 'B22', 'B40', 'B41', 'B42', 'B43', 'B44', 'B60', 'B61', 'B62', 'B63', 'B64', 'B65', 'B66']
-    Ions = ['Ce', 'Pr', 'Nd', 'Pm', 'Sm', 'Eu', 'Gd', 'Tb', 'Dy', 'Ho', 'Er', 'Tm', 'Yb']
     # Some Error checking
     if 'Ion' not in kwargs.keys() and 'IonNum' not in kwargs.keys():
         raise NameError('You must specify the ion using either the ''Ion'', ''IonNum'' keywords')
     if 'Ion' in kwargs.keys():
-        nre = [id for id, val in enumerate(Ions) if val == kwargs['Ion']][0] + 1
+        nre = ionname2Nre(kwargs['Ion'])
     else:
         nre = kwargs['IonNum']
     # Now parses the list of input crystal field parameters
@@ -184,9 +185,8 @@ def split2range(*args, **kwargs):
     # Error checking
     if 'Ion' not in argin.keys() and 'IonNum' not in argin.keys():
         raise NameError('You must specify the ion using either the ''Ion'', ''IonNum'' keywords')
-    Ions = ['Ce', 'Pr', 'Nd', 'Pm', 'Sm', 'Eu', 'Gd', 'Tb', 'Dy', 'Ho', 'Er', 'Tm', 'Yb']
     if 'Ion' in argin.keys():
-        nre = [ id for id,val in enumerate(Ions) if val==kwargs['Ion'] ][0] + 1
+        nre = ionname2Nre(kwargs['Ion'])
     else:
         nre = argin['IonNum']
     if 'EnergySplitting' not in argin.keys():
diff --git a/scripts/test/CrystalFieldMultiSiteTest.py b/scripts/test/CrystalFieldMultiSiteTest.py
index 96b7af7b73e404343ff8a3d1e8337ca482e174c7..dc8c8404c535a7f156ede63623c8f2113d0765c3 100644
--- a/scripts/test/CrystalFieldMultiSiteTest.py
+++ b/scripts/test/CrystalFieldMultiSiteTest.py
@@ -9,19 +9,19 @@ c_mbsr = 79.5774715459  # Conversion from barn to mb/sr
 class CrystalFieldMultiSiteTests(unittest.TestCase):
 
     def test_init_single_ion(self):
-        cfms = CrystalFieldMultiSite(Ions='Pm', Symmetries='D2', Temperatures=[20, 52], FWHMs=[1.0, 1.0])
+        cfms = CrystalFieldMultiSite(Ions='Pm', Symmetries='D2', Temperatures=[20, 52], FWHM=[1.0, 1.0])
         self.assertEqual(cfms.Ions, ['Pm'])
         self.assertEqual(cfms.Symmetries, ['D2'])
         self.assertEqual(cfms.Temperatures, [20, 52])
-        self.assertEqual(cfms.FWHMs, [1.0, 1.0])
+        self.assertEqual(cfms.FWHM, [1.0, 1.0])
 
     def test_init_multi_ions(self):
         cfms = CrystalFieldMultiSite(Ions=('Pm', 'Eu'), Symmetries=('D2', 'C3v'), Temperatures=[20, 52],
-                                     FWHMs=[1.0, 1.0])
+                                     FWHM=[1.0, 1.0])
         self.assertEqual(cfms.Ions, ['Pm', 'Eu'])
         self.assertEqual(cfms.Symmetries, ['D2', 'C3v'])
         self.assertEqual(cfms.Temperatures, [20, 52])
-        self.assertEqual(cfms.FWHMs, [1.0, 1.0])
+        self.assertEqual(cfms.FWHM, [1.0, 1.0])
 
     def test_toleranceEnergy_property(self):
         cfms = CrystalFieldMultiSite(('Pm', 'Eu'), ('D2', 'C3v'), ToleranceEnergy=1.0)
@@ -48,28 +48,28 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
         self.assertEqual(cfms.FWHMVariation, 0.3)
 
     def test_init_parameters(self):
-        cfms = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2', Temperatures=[15], FWHMs=[1.0],
+        cfms = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2', Temperatures=[15], FWHM=[1.0],
                                      parameters={'BmolX': 1.0, 'B40': -0.02})
         self.assertEqual(cfms.function.getParameterValue('BmolX'), 1.0)
         self.assertEqual(cfms.function.getParameterValue('B40'), -0.02)
 
     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})
+                                     FWHM=[1.0, 1.0], parameters={'ion0.B40': -0.02, 'ion0.B42': -0.11,
+                                                                  'ion1.B42': -0.12})
         self.assertEqual(cfms.function.getParameterValue('ion0.B42'), -0.11)
         self.assertEqual(cfms.function.getParameterValue('ion0.B40'), -0.02)
         self.assertEqual(cfms.function.getParameterValue('ion1.B42'), -0.12)
 
     def test_peak_values(self):
-        cfms = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2', Temperatures=[25], FWHMs=[1.5],
+        cfms = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2', Temperatures=[25], FWHM=[1.5],
                                      BmolX=1.0, B40=-0.02)
         self.assertEqual(int(cfms.function.getParameterValue('pk0.Amplitude')), 48)
         self.assertEqual(cfms.function.getParameterValue('pk0.FWHM'), 1.5)
         self.assertEqual(cfms.function.getParameterValue('pk0.PeakCentre'), 0)
 
     def test_peak_values_gaussian(self):
-        cfms = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2', Temperatures=[25], FWHMs=[1.0], PeakShape='Gaussian',
+        cfms = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2', Temperatures=[25], FWHM=[1.0], PeakShape='Gaussian',
                                      BmolX=1.0, B40=-0.02)
         self.assertEqual(int(cfms.function.getParameterValue('pk0.Height')), 45)
         self.assertAlmostEqual(cfms.function.getParameterValue('pk0.Sigma'), 0.42, 2)
@@ -77,7 +77,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
 
     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})
+                                     FWHM=[1.5], parameters={'ion0.B60': -0.02, 'ion1.B62': -0.12})
         self.assertEqual(int(cfms.function.getParameterValue('ion0.pk0.Amplitude')), 278)
         self.assertEqual(cfms.function.getParameterValue('ion0.pk0.FWHM'), 1.5)
         self.assertEqual(cfms.function.getParameterValue('ion0.pk1.PeakCentre'), 982.8)
@@ -87,7 +87,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
 
     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})
+                                     FWHM=[1.0, 1.0], parameters={'ion0.B40': -0.02, 'ion1.B42': -0.12})
         self.assertEqual(int(cfms.function.getParameterValue('ion0.sp0.pk0.Amplitude')), 308)
         self.assertEqual(cfms.function.getParameterValue('ion0.sp1.pk0.FWHM'), 1.0)
         self.assertEqual(cfms.function.getParameterValue('ion0.sp0.pk1.PeakCentre'), 0)
@@ -97,7 +97,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
 
     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},
+                                     FWHM=[1.0, 1.5], parameters={'ion0.B40': -0.02, 'ion1.B42': -0.12},
                                      PeakShape='Gaussian')
         self.assertEqual(int(cfms.function.getParameterValue('ion0.sp0.pk0.Height')), 289)
         self.assertAlmostEqual(cfms.function.getParameterValue('ion0.sp1.pk0.Sigma'), 0.64, 2)
@@ -107,7 +107,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
         self.assertAlmostEqual(cfms.function.getParameterValue('ion1.sp0.pk1.PeakCentre'), 40.957515, 6)
 
     def test_get_spectrum_from_list(self):
-        cfms = CrystalFieldMultiSite(Ions=['Ce'], Symmetries=['C2v'], Temperatures=[4.0], FWHMs=[0.1],
+        cfms = CrystalFieldMultiSite(Ions=['Ce'], Symmetries=['C2v'], Temperatures=[4.0], FWHM=[0.1],
                                      B20=0.035, B40=-0.012, B43=-0.027, B60=-0.00012, B63=0.0025, B66=0.0068,
                                      ToleranceIntensity=0.001*c_mbsr)
         r = [0.0, 1.45, 2.4, 3.0, 3.85]
@@ -120,7 +120,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
     def test_get_spectrum_ws(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], FWHMs=[0.1], ToleranceIntensity=0.001*c_mbsr)
+                                     B66=0.0068, Temperatures=[4.0], FWHM=[0.1], ToleranceIntensity=0.001*c_mbsr)
 
         x = np.linspace(0.0, 2.0, 30)
         y = np.zeros_like(x)
@@ -146,7 +146,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
         self.assertAlmostEqual(y[1], 0.054428, 6)
 
     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],
+        cfms = CrystalFieldMultiSite(Ions=['Ce'], Symmetries=['C2v'], Temperatures=[4.0, 50.0], FWHM=[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]
         x, y = cfms.getSpectrum(0, r)
@@ -164,7 +164,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
     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])
+                          Temperatures=[4.0, 50.0], FWHM=[0.1, 0.2])
 
         x = np.linspace(0.0, 2.0, 30)
         y = np.zeros_like(x)
@@ -201,7 +201,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
                   '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)
+                                     FWHM=[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
@@ -233,7 +233,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
         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], FWHMs=[1.1],
+        cf = CrystalFieldMultiSite(Ions=['Ce', 'Pr'], Symmetries=['C2v', 'C2v'], Temperatures=[44.0], FWHM=[1.1],
                                    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',
@@ -267,7 +267,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
                   '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,
+                                    FWHM=[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',
@@ -285,7 +285,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
         self.assertTrue(cf.chi2 < chi2)
 
     def test_set_background(self):
-        cf = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2v', Temperatures=[20], FWHMs=[1.0], Background='name=LinearBackground,A0=1')
+        cf = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2v', Temperatures=[20], FWHM=[1.0], Background='name=LinearBackground,A0=1')
         self.assertEquals('"name=LinearBackground,A0=1"', cf['Background'])
         self.assertEquals(cf.background.param['A0'], 1)
         self.assertEquals(cf.background.background.param['A0'], 1)
@@ -294,7 +294,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
 
     def test_set_background_as_function(self):
         from mantid.fitfunctions import LinearBackground
-        cf = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2v', Temperatures=[20], FWHMs=[1.0],
+        cf = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2v', Temperatures=[20], FWHM=[1.0],
                                    Background=LinearBackground(A0=1))
         self.assertEquals('"name=LinearBackground,A0=1,A1=0"', cf['Background'])
         self.assertEquals(cf.background.param['A0'], 1)
@@ -304,7 +304,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
 
     def test_set_background_with_peak(self):
         from mantid.fitfunctions import Gaussian
-        cf = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2v', Temperatures=[20], FWHMs=[1.0], Background='name=LinearBackground', BackgroundPeak=Gaussian(Height=1))
+        cf = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2v', Temperatures=[20], FWHM=[1.0], Background='name=LinearBackground', BackgroundPeak=Gaussian(Height=1))
         self.assertEquals('"name=Gaussian,Height=1,PeakCentre=0,Sigma=0;name=LinearBackground"', cf['Background'])
         self.assertEquals(cf.background.peak.param['Height'], 1)
         self.assertEquals(cf.background.param['f0.Height'], 1)
@@ -316,7 +316,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
 
     def test_set_background_peak_only(self):
         from mantid.fitfunctions import Gaussian
-        cf = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2v', Temperatures=[20], FWHMs=[1.0], BackgroundPeak=Gaussian(Sigma=1))
+        cf = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2v', Temperatures=[20], FWHM=[1.0], BackgroundPeak=Gaussian(Sigma=1))
         self.assertEquals('"name=Gaussian,Height=0,PeakCentre=0,Sigma=1"', cf['Background'])
         self.assertEquals(cf.background.peak.param['Sigma'], 1)
         self.assertEquals(cf.background.param['Sigma'], 1)
@@ -325,7 +325,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
 
     def test_set_background_composite(self):
         from mantid.fitfunctions import Gaussian, LinearBackground
-        cf = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2v', Temperatures=[20], FWHMs=[1.0],
+        cf = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2v', Temperatures=[20], FWHM=[1.0],
                                    Background= Gaussian(PeakCentre=1) + LinearBackground())
         self.assertEquals('"name=Gaussian,Height=0,PeakCentre=1,Sigma=0;name=LinearBackground,A0=0,A1=0"', cf['Background'])
         cf.background.param['f1.A0'] = 1
@@ -334,7 +334,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
         self.assertEquals(cf.background.param['f0.PeakCentre'], 0.5)
 
     def test_set_background_composite_as_string(self):
-        cf = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2v', Temperatures=[20], FWHMs=[1.0],
+        cf = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2v', Temperatures=[20], FWHM=[1.0],
                                    Background='name=Gaussian,Height=0,PeakCentre=1,Sigma=0;name=LinearBackground,A0=0,A1=0')
         self.assertEquals('"name=Gaussian,Height=0,PeakCentre=1,Sigma=0;name=LinearBackground,A0=0,A1=0"', cf['Background'])
         self.assertEquals(cf.background.param['f0.Sigma'], 0)
@@ -345,7 +345,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
         from mantid.fitfunctions import Gaussian, LinearBackground
         from mantid.simpleapi import FunctionFactory
 
-        cf = CrystalFieldMultiSite(Ions=['Ce'], Symmetries=['C2v'], Temperatures=[50], FWHMs=[0.9],
+        cf = CrystalFieldMultiSite(Ions=['Ce'], Symmetries=['C2v'], Temperatures=[50], FWHM=[0.9],
                                    B20=0.37737, B22=3.9770, B40=-0.031787, B42=-0.11611, B44=-0.12544,
                                    Background=LinearBackground(A0=1.0), BackgroundPeak=Gaussian(Height=10, Sigma=0.3))
 
@@ -377,7 +377,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
     def test_constraints_multi_spectrum(self):
         from mantid.fitfunctions import FlatBackground, Gaussian
 
-        cf = CrystalFieldMultiSite(Ions=['Ce'], Symmetries=['C2v'], Temperatures=[44, 50], FWHMs=[1.1, 0.9],
+        cf = CrystalFieldMultiSite(Ions=['Ce'], Symmetries=['C2v'], Temperatures=[44, 50], FWHM=[1.1, 0.9],
                                    Background=FlatBackground(), BackgroundPeak=Gaussian(Height=10, Sigma=0.3),
                                    B20=0.37737, B22=3.9770, B40=-0.031787, B42=-0.11611, B44=-0.12544)
 
@@ -408,7 +408,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
     def test_constraints_multi_spectrum_and_ion(self):
         from mantid.fitfunctions import FlatBackground, Gaussian
 
-        cf = CrystalFieldMultiSite(Ions=['Ce','Pr'], Symmetries=['C2v', 'C2v'], Temperatures=[44, 50], FWHMs=[1.1, 0.9],
+        cf = CrystalFieldMultiSite(Ions=['Ce','Pr'], Symmetries=['C2v', 'C2v'], Temperatures=[44, 50], FWHM=[1.1, 0.9],
                                    Background=FlatBackground(), BackgroundPeak=Gaussian(Height=10, Sigma=0.3),
                                    parameters={'ion0.B20': 0.37737, 'ion0.B22': 3.9770, 'ion1.B40':-0.031787,
                                                'ion1.B42':-0.11611, 'ion1.B44':-0.12544})
@@ -440,7 +440,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
     def test_add_CrystalField(self):
         from CrystalField import CrystalField
 
-        cfms = CrystalFieldMultiSite(Ions=['Pr'], Symmetries=['C2v'], Temperatures=[44, 50], FWHMs=[1.1, 0.9],
+        cfms = CrystalFieldMultiSite(Ions=['Pr'], Symmetries=['C2v'], Temperatures=[44, 50], FWHM=[1.1, 0.9],
                                      B20=0.37737, B22=3.9770, B40=-0.031787, B42=-0.11611, B44=-0.15)
 
         params = {'B20': 0.37737, 'B22': 3.9770, 'B40': -0.031787, 'B42': -0.11611, 'B44': -0.12544}
@@ -463,7 +463,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
     def test_add_CrystalFieldSite(self):
         from CrystalField import CrystalField
 
-        cfms = CrystalFieldMultiSite(Ions=['Pr'], Symmetries=['C2v'], Temperatures=[44, 50], FWHMs=[1.1, 0.9],
+        cfms = CrystalFieldMultiSite(Ions=['Pr'], Symmetries=['C2v'], Temperatures=[44, 50], FWHM=[1.1, 0.9],
                                      B20=0.37737, B22=3.9770, B40=-0.031787, B42=-0.11611, B44=-0.15)
 
         params = {'B20': 0.37737, 'B22': 3.9770, 'B40': -0.031787, 'B42': -0.11611, 'B44': -0.12544}
@@ -482,11 +482,11 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
         self.assertTrue('ion0.IntensityScaling=0.125*ion1.IntensityScaling' in s)
 
     def test_add_CrystalFieldMultiSite(self):
-        cfms1 = CrystalFieldMultiSite(Ions=['Pm'], Symmetries=['D2'], Temperatures=[44, 50], FWHMs=[1.1, 0.9],
+        cfms1 = CrystalFieldMultiSite(Ions=['Pm'], Symmetries=['D2'], Temperatures=[44, 50], FWHM=[1.1, 0.9],
                                      B20=0.37737, B40=-0.031787, B42=-0.11611, B44=-0.15)
         cfms2 = CrystalFieldMultiSite(Ions=['Ce', 'Pr'], Symmetries=['C2v', 'C2v'], Temperatures=[44, 50],
-                                      FWHMs=[1.1, 0.9], parameters={'ion0.B20': 0.34, 'ion0.B22': 3.9770,
-                                                                    'ion1.B40': -0.03})
+                                      FWHM=[1.1, 0.9], parameters={'ion0.B20': 0.34, 'ion0.B22': 3.9770,
+                                                                   'ion1.B40': -0.03})
         cfms3 = cfms1 + cfms2
         self.assertEqual(len(cfms3.Ions), 3)
         self.assertEqual(len(cfms3.Symmetries), 3)
diff --git a/scripts/test/CrystalFieldTest.py b/scripts/test/CrystalFieldTest.py
index 5fa0706c89f93135f46d73dbf903b3b4ec54c0bc..b704f56075dc1feff282ccc94a2b3bfb17abcf06 100644
--- a/scripts/test/CrystalFieldTest.py
+++ b/scripts/test/CrystalFieldTest.py
@@ -1105,14 +1105,14 @@ class CrystalFieldFitTest(unittest.TestCase):
     def test_ResolutionModel_func_multi(self):
         from CrystalField import ResolutionModel
         def func0(x):
-            return np.sin(x)
+            return np.sin(np.array(x))
 
         class CalcWidth:
             def __call__(self, x):
-                return np.cos(x / 2)
+                return np.cos(np.array(x) / 2)
 
             def model(self, x):
-                return np.tan(x / 2)
+                return np.tan(np.array(x) / 2)
         func1 = CalcWidth()
         func2 = func1.model
         rm = ResolutionModel([func0, func1, func2], -np.pi/2, np.pi/2, accuracy = 0.01)