Skip to content
Snippets Groups Projects
ConvFit.cpp 55.4 KiB
Newer Older
#include "MantidQtCustomInterfaces/Indirect/ConvFit.h"
#include "MantidQtCustomInterfaces/UserInputValidator.h"
#include "MantidQtMantidWidgets/RangeSelector.h"

#include "MantidAPI/AlgorithmManager.h"
#include "MantidAPI/FunctionDomain1D.h"
#include "MantidAPI/FunctionFactory.h"
#include "MantidAPI/TextAxis.h"
#include <QDoubleValidator>
#include <QFileInfo>
#include <QMenu>

#include <qwt_plot.h>
#include <qwt_plot_curve.h>

using namespace Mantid::API;

namespace {
Mantid::Kernel::Logger g_log("ConvFit");
namespace MantidQt {
namespace CustomInterfaces {
namespace IDA {
ConvFit::ConvFit(QWidget *parent)
    : IndirectDataAnalysisTab(parent), m_stringManager(NULL), m_cfTree(NULL),
      m_fixedProps(), m_cfInputWS(), m_cfInputWSName(), m_confitResFileType() {
  m_uiForm.setupUi(parent);
}
void ConvFit::setup() {
  // Create Property Managers
  m_stringManager = new QtStringPropertyManager();
  m_runMin = 0;
  m_runMax = 0;
  // Initialise fitTypeStrings
  m_fitStrings = QStringList() << ""
                               << "1L"
                               << "2L"
                               << "IDS"
                               << "IDC"
                               << "EDS"
                               << "EDC"
                               << "SFT";

  // Create TreeProperty Widget
  m_cfTree = new QtTreePropertyBrowser();
  m_uiForm.properties->addWidget(m_cfTree);

  // add factories to managers
  m_cfTree->setFactoryForManager(m_blnManager, m_blnEdFac);
  m_cfTree->setFactoryForManager(m_dblManager, m_dblEdFac);

  // Create Range Selectors
  auto fitRangeSelector = m_uiForm.ppPlot->addRangeSelector("ConvFitRange");
  auto backRangeSelector = m_uiForm.ppPlot->addRangeSelector(
      "ConvFitBackRange", MantidWidgets::RangeSelector::YSINGLE);
  auto hwhmRangeSelector = m_uiForm.ppPlot->addRangeSelector("ConvFitHWHM");
  backRangeSelector->setColour(Qt::darkGreen);
  backRangeSelector->setRange(0.0, 1.0);
  hwhmRangeSelector->setColour(Qt::red);

  // Populate Property Widget

  // Option to convolve members
  m_properties["Convolve"] = m_blnManager->addProperty("Convolve");
  m_cfTree->addProperty(m_properties["Convolve"]);
  m_blnManager->setValue(m_properties["Convolve"], true);

  // Max iterations option
  m_properties["MaxIterations"] = m_dblManager->addProperty("Max Iterations");
  m_dblManager->setDecimals(m_properties["MaxIterations"], 0);
  m_dblManager->setValue(m_properties["MaxIterations"], 500);
  m_cfTree->addProperty(m_properties["MaxIterations"]);

  // Fitting range
  m_properties["FitRange"] = m_grpManager->addProperty("Fitting Range");
  m_properties["StartX"] = m_dblManager->addProperty("StartX");
  m_dblManager->setDecimals(m_properties["StartX"], NUM_DECIMALS);
  m_properties["EndX"] = m_dblManager->addProperty("EndX");
  m_dblManager->setDecimals(m_properties["EndX"], NUM_DECIMALS);
  m_properties["FitRange"]->addSubProperty(m_properties["StartX"]);
  m_properties["FitRange"]->addSubProperty(m_properties["EndX"]);
  m_cfTree->addProperty(m_properties["FitRange"]);

  // FABADA
  m_properties["FABADA"] = m_grpManager->addProperty("Bayesian");
  m_properties["UseFABADA"] = m_blnManager->addProperty("Use FABADA");
  m_properties["FABADA"]->addSubProperty(m_properties["UseFABADA"]);
  m_properties["OutputFABADAChain"] = m_blnManager->addProperty("Output Chain");
  m_properties["FABADAChainLength"] = m_dblManager->addProperty("Chain Length");
  m_dblManager->setDecimals(m_properties["FABADAChainLength"], 0);
  m_dblManager->setValue(m_properties["FABADAChainLength"], 1000000);
  m_properties["FABADAConvergenceCriteria"] =
      m_dblManager->addProperty("Convergence Criteria");
  m_dblManager->setValue(m_properties["FABADAConvergenceCriteria"], 0.1);
  m_properties["FABADAJumpAcceptanceRate"] =
      m_dblManager->addProperty("Acceptance Rate");
  m_dblManager->setValue(m_properties["FABADAJumpAcceptanceRate"], 0.25);
  m_cfTree->addProperty(m_properties["FABADA"]);

  // Background type
  m_properties["LinearBackground"] = m_grpManager->addProperty("Background");
  m_properties["BGA0"] = m_dblManager->addProperty("A0");
  m_dblManager->setDecimals(m_properties["BGA0"], NUM_DECIMALS);
  m_properties["BGA1"] = m_dblManager->addProperty("A1");
  m_dblManager->setDecimals(m_properties["BGA1"], NUM_DECIMALS);
  m_properties["LinearBackground"]->addSubProperty(m_properties["BGA0"]);
  m_properties["LinearBackground"]->addSubProperty(m_properties["BGA1"]);
  m_cfTree->addProperty(m_properties["LinearBackground"]);

  // Delta Function
  m_properties["DeltaFunction"] = m_grpManager->addProperty("Delta Function");
  m_properties["UseDeltaFunc"] = m_blnManager->addProperty("Use");
  m_properties["DeltaHeight"] = m_dblManager->addProperty("Height");
  m_dblManager->setDecimals(m_properties["DeltaHeight"], NUM_DECIMALS);
  m_properties["DeltaFunction"]->addSubProperty(m_properties["UseDeltaFunc"]);
  m_cfTree->addProperty(m_properties["DeltaFunction"]);

  // Fit functions
  m_properties["Lorentzian1"] = createFitType("Lorentzian 1");
  m_properties["Lorentzian2"] = createFitType("Lorentzian 2");
  m_properties["DiffSphere"] = createFitType("DiffSphere");
  m_properties["DiffRotDiscreteCircle"] =
      createFitType("DiffRotDiscreteCircle");
  m_properties["ElasticDiffSphere"] = createFitType("ElasticDiffSphere");
  m_properties["ElasticDiffRotDiscreteCircle"] =
      createFitType("ElasticDiffRotDiscreteCircle");
  m_properties["InelasticDiffSphere"] = createFitType("InelasticDiffSphere");
  m_properties["InelasticDiffRotDiscreteCircle"] =
      createFitType("InelasticDiffRotDiscreteCircle");
  m_properties["StretchedExpFT"] = createFitType("StretchedExpFT");

  // Update fit parameters in browser when function is selected
  connect(m_uiForm.cbFitType, SIGNAL(currentIndexChanged(QString)), this,
          SLOT(fitFunctionSelected(const QString &)));
  fitFunctionSelected(m_uiForm.cbFitType->currentText());

  m_uiForm.leTempCorrection->setValidator(new QDoubleValidator(m_parentWidget));

  // Connections
  connect(fitRangeSelector, SIGNAL(minValueChanged(double)), this,
          SLOT(minChanged(double)));
  connect(fitRangeSelector, SIGNAL(maxValueChanged(double)), this,
          SLOT(maxChanged(double)));
  connect(backRangeSelector, SIGNAL(minValueChanged(double)), this,
          SLOT(backgLevel(double)));
  connect(hwhmRangeSelector, SIGNAL(minValueChanged(double)), this,
          SLOT(hwhmChanged(double)));
  connect(hwhmRangeSelector, SIGNAL(maxValueChanged(double)), this,
          SLOT(hwhmChanged(double)));
  connect(m_dblManager, SIGNAL(valueChanged(QtProperty *, double)), this,
          SLOT(updateRS(QtProperty *, double)));
  connect(m_blnManager, SIGNAL(valueChanged(QtProperty *, bool)), this,
          SLOT(checkBoxUpdate(QtProperty *, bool)));
  connect(m_uiForm.ckTempCorrection, SIGNAL(toggled(bool)),
          m_uiForm.leTempCorrection, SLOT(setEnabled(bool)));

  // Update guess curve when certain things happen
  connect(m_dblManager, SIGNAL(propertyChanged(QtProperty *)), this,
          SLOT(plotGuess()));
  connect(m_uiForm.cbFitType, SIGNAL(currentIndexChanged(int)), this,
          SLOT(plotGuess()));
  connect(m_uiForm.ckPlotGuess, SIGNAL(stateChanged(int)), this,
          SLOT(plotGuess()));

  // Have FWHM Range linked to Fit Start/End Range
  connect(fitRangeSelector, SIGNAL(rangeChanged(double, double)),
          hwhmRangeSelector, SLOT(setRange(double, double)));
  hwhmRangeSelector->setRange(-1.0, 1.0);
  hwhmUpdateRS(0.02);

  typeSelection(m_uiForm.cbFitType->currentIndex());
  bgTypeSelection(m_uiForm.cbBackground->currentIndex());

  // Replot input automatically when file / spec no changes
  connect(m_uiForm.spPlotSpectrum, SIGNAL(valueChanged(int)), this,
          SLOT(updatePlot()));
  connect(m_uiForm.dsSampleInput, SIGNAL(dataReady(const QString &)), this,
          SLOT(newDataLoaded(const QString &)));

  connect(m_uiForm.dsSampleInput, SIGNAL(dataReady(const QString &)), this,
          SLOT(extendResolutionWorkspace()));
  connect(m_uiForm.dsResInput, SIGNAL(dataReady(const QString &)), this,
          SLOT(extendResolutionWorkspace()));

  connect(m_uiForm.spSpectraMin, SIGNAL(valueChanged(int)), this,
          SLOT(specMinChanged(int)));
  connect(m_uiForm.spSpectraMax, SIGNAL(valueChanged(int)), this,
          SLOT(specMaxChanged(int)));

  connect(m_uiForm.cbFitType, SIGNAL(currentIndexChanged(int)), this,
          SLOT(typeSelection(int)));
  connect(m_uiForm.cbBackground, SIGNAL(currentIndexChanged(int)), this,
          SLOT(bgTypeSelection(int)));
  connect(m_uiForm.pbSingleFit, SIGNAL(clicked()), this, SLOT(singleFit()));

  // Context menu
  m_cfTree->setContextMenuPolicy(Qt::CustomContextMenu);
  connect(m_cfTree, SIGNAL(customContextMenuRequested(const QPoint &)), this,
          SLOT(fitContextMenu(const QPoint &)));

  // Tie
  connect(m_uiForm.cbFitType, SIGNAL(currentIndexChanged(QString)),
          SLOT(showTieCheckbox(QString)));
  showTieCheckbox(m_uiForm.cbFitType->currentText());
  m_previousFit = m_uiForm.cbFitType->currentText();

  updatePlotOptions();
 * Handles the initial set up and running of the ConvolutionFitSequential
 * algorithm
void ConvFit::run() {
  if (m_cfInputWS == NULL) {
    g_log.error("No workspace loaded");
    return;
  }

  QString fitType = fitTypeString();
  QString bgType = backgroundString();
  if (fitType == "") {
    g_log.error("No fit type defined");
  }
  bool useTies = m_uiForm.ckTieCentres->isChecked();
  QString ties = (useTies ? "True" : "False");
  CompositeFunction_sptr func = createFunction(useTies);
  std::string function = std::string(func->asString());
  std::string stX = m_properties["StartX"]->valueText().toStdString();
  std::string enX = m_properties["EndX"]->valueText().toStdString();
  m_runMin = m_uiForm.spSpectraMin->value();
  m_runMax = m_uiForm.spSpectraMax->value();
  std::string specMin = m_uiForm.spSpectraMin->text().toStdString();
  std::string specMax = m_uiForm.spSpectraMax->text().toStdString();
  int maxIterations =
      static_cast<int>(m_dblManager->value(m_properties["MaxIterations"]));
  // Construct expected name
  m_baseName = QString::fromStdString(m_cfInputWS->getName());
  int pos = m_baseName.lastIndexOf("_");
  if (pos != -1) {
    m_baseName = m_baseName.left(pos + 1);
  }
  m_baseName += "conv_";
  if (m_blnManager->value(m_properties["UseDeltaFunc"])) {
    m_baseName += "Delta";
  }
  int fitIndex = m_uiForm.cbFitType->currentIndex();
  if (fitIndex < 3 && fitIndex != 0) {
    m_baseName += QString::number(fitIndex);
    m_baseName += "L";
  } else {
    m_baseName += convertFuncToShort(m_uiForm.cbFitType->currentText());
  }
  m_baseName +=
      convertBackToShort(m_uiForm.cbBackground->currentText().toStdString()) +
      "_s";
  m_baseName += QString::fromStdString(specMin);
  m_baseName += "_to_";
  m_baseName += QString::fromStdString(specMax);

  // Run ConvolutionFitSequential Algorithm
  IAlgorithm_sptr cfs =
      AlgorithmManager::Instance().create("ConvolutionFitSequential");
  cfs->initialize();

  cfs->setProperty("InputWorkspace", m_cfInputWS->getName());
  cfs->setProperty("Function", function);
  cfs->setProperty("BackgroundType",
                   m_uiForm.cbBackground->currentText().toStdString());
  cfs->setProperty("StartX", stX);
  cfs->setProperty("EndX", enX);
  cfs->setProperty("SpecMin", specMin);
  cfs->setProperty("SpecMax", specMax);
  cfs->setProperty("Convolve", true);
  cfs->setProperty("Minimizer",
                   minimizerString("$outputname_$wsindex").toStdString());
  cfs->setProperty("MaxIterations", maxIterations);
  m_batchAlgoRunner->addAlgorithm(cfs);
  connect(m_batchAlgoRunner, SIGNAL(batchComplete(bool)), this,
          SLOT(algorithmComplete(bool)));
  m_batchAlgoRunner->executeBatchAsync();
}
/**
 * Handles completion of the ConvolutionFitSequential algorithm.
 *
 * @param error True if the algorithm was stopped due to error, false otherwise
 */
void ConvFit::algorithmComplete(bool error) {
  disconnect(m_batchAlgoRunner, SIGNAL(batchComplete(bool)), this,
             SLOT(algorithmComplete(bool)));
  std::string resultName = m_baseName.toStdString() + "_Result";
  MatrixWorkspace_sptr resultWs =
      AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(resultName);

  const bool save = m_uiForm.ckSave->isChecked();

  if (save) {
    QString saveDir = QString::fromStdString(
        Mantid::Kernel::ConfigService::Instance().getString(
            "defaultsave.directory"));
    // Check validity of save path
    QString QresultWsName = QString::fromStdString(resultWs->getName());
    QString fullPath = saveDir.append(QresultWsName).append(".nxs");
    addSaveWorkspaceToQueue(QresultWsName, fullPath);
  }

  std::string plot = m_uiForm.cbPlotType->currentText().toStdString();

  // Handle plot result
  if (!(plot.compare("None") == 0)) {
    if (plot.compare("All") == 0) {
      int specEnd = (int)resultWs->getNumberHistograms();
      for (int i = 0; i < specEnd; i++) {
        IndirectTab::plotSpectrum(QString::fromStdString(resultWs->getName()),
                                  i, i);
      }
    } else {
      // -1 to account for None in dropDown
      int specNumber = m_uiForm.cbPlotType->currentIndex() - 1;
      IndirectTab::plotSpectrum(QString::fromStdString(resultWs->getName()),
                                specNumber, specNumber);
  if (m_uiForm.ckTempCorrection->isChecked()) {
    QString temperature = m_uiForm.leTempCorrection->text();
    double temp = 0.0;
    if (temperature.toStdString().compare("") != 0) {
      temp = temperature.toDouble();
    }
    if (temp != 0.0) {
      // Obtain WorkspaceGroup from ADS
      std::string groupName = m_baseName.toStdString() + "_Workspaces";
      WorkspaceGroup_sptr groupWs =
          AnalysisDataService::Instance().retrieveWS<WorkspaceGroup>(groupName);

      auto addSample = AlgorithmManager::Instance().create("AddSampleLog");
      addSample->setProperty("Workspace", resultWs);
      addSample->setProperty("LogName", "temperature_value");
      addSample->setProperty("LogText", temperature.toStdString());
      addSample->setProperty("LogType", "Number");
      addSample->execute();
      addSample->setProperty("Workspace", resultWs);
      addSample->setProperty("LogName", "temperature_correction");
      addSample->setProperty("LogText", "true");
      addSample->setProperty("LogType", "String");
      addSample->execute();
      addSample->setProperty("Workspace", groupWs);
      addSample->setProperty("LogName", "temperature_value");
      addSample->setProperty("LogText", temperature.toStdString());
      addSample->setProperty("LogType", "Number");
      addSample->execute();
      addSample->setProperty("Workspace", groupWs);
      addSample->setProperty("LogName", "temperature_correction");
      addSample->setProperty("LogText", "true");
      addSample->setProperty("LogType", "String");
      addSample->execute();
    }
  m_batchAlgoRunner->executeBatchAsync();
/**
 * Validates the user's inputs in the ConvFit tab.
 * @return If the validation was successful
 */
bool ConvFit::validate() {
  UserInputValidator uiv;
  uiv.checkDataSelectorIsValid("Sample", m_uiForm.dsSampleInput);
  uiv.checkDataSelectorIsValid("Resolution", m_uiForm.dsResInput);
  auto range = std::make_pair(m_dblManager->value(m_properties["StartX"]),
                              m_dblManager->value(m_properties["EndX"]));
  uiv.checkValidRange("Fitting Range", range);
  // Enforce the rule that at least one fit is needed; either a delta function,
  // one or two lorentzian functions,
  // or both.  (The resolution function must be convolved with a model.)
  if (m_uiForm.cbFitType->currentIndex() == 0 &&
      !m_blnManager->value(m_properties["UseDeltaFunc"]))
    uiv.addErrorMessage("No fit function has been selected.");
  QString error = uiv.generateErrorMessage();
  showMessageBox(error);
  return error.isEmpty();
}
/**
 * Reads in settings files
 * @param settings The name of the QSettings object to retrieve data from
 */
void ConvFit::loadSettings(const QSettings &settings) {
  m_uiForm.dsSampleInput->readSettings(settings.group());
  m_uiForm.dsResInput->readSettings(settings.group());
}
/**
 * Called when new data has been loaded by the data selector.
 *
 * Configures ranges for spin boxes before raw plot is done.
 *
 * @param wsName Name of new workspace loaded
 */
void ConvFit::newDataLoaded(const QString wsName) {
  m_cfInputWSName = wsName;
  m_cfInputWS = AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(
      m_cfInputWSName.toStdString());
  int maxSpecIndex = static_cast<int>(m_cfInputWS->getNumberHistograms()) - 1;
  m_uiForm.spPlotSpectrum->setMaximum(maxSpecIndex);
  m_uiForm.spPlotSpectrum->setMinimum(0);
  m_uiForm.spPlotSpectrum->setValue(0);
  m_uiForm.spSpectraMin->setMaximum(maxSpecIndex);
  m_uiForm.spSpectraMin->setMinimum(0);
  m_uiForm.spSpectraMax->setMaximum(maxSpecIndex);
  m_uiForm.spSpectraMax->setMinimum(0);
  m_uiForm.spSpectraMax->setValue(maxSpecIndex);
/**
 * Create a resolution workspace with the same number of histograms as in the
 * sample.
 *
 * Needed to allow DiffSphere and DiffRotDiscreteCircle fit functions to work as
 * they need
 * to have the WorkspaceIndex attribute set.
 */
void ConvFit::extendResolutionWorkspace() {
  if (m_cfInputWS && m_uiForm.dsResInput->isValid()) {
    const QString resWsName = m_uiForm.dsResInput->getCurrentDataName();
    API::BatchAlgorithmRunner::AlgorithmRuntimeProps appendProps;
    appendProps["InputWorkspace1"] = "__ConvFit_Resolution";

    size_t numHist = m_cfInputWS->getNumberHistograms();
    for (size_t i = 0; i < numHist; i++) {
      IAlgorithm_sptr appendAlg =
          AlgorithmManager::Instance().create("AppendSpectra");
      appendAlg->initialize();
      appendAlg->setProperty("InputWorkspace2", resWsName.toStdString());
      appendAlg->setProperty("OutputWorkspace", "__ConvFit_Resolution");

      if (i == 0) {
        appendAlg->setProperty("InputWorkspace1", resWsName.toStdString());
        m_batchAlgoRunner->addAlgorithm(appendAlg);
      } else {
        m_batchAlgoRunner->addAlgorithm(appendAlg, appendProps);
    m_batchAlgoRunner->executeBatchAsync();
  }
}
namespace {
////////////////////////////
// Anon Helper functions. //
////////////////////////////

/**
 * Takes an index and a name, and constructs a single level parameter name
 * for use with function ties, etc.
 *
 * @param index :: the index of the function in the first level.
 * @param name  :: the name of the parameter inside the function.
 *
 * @returns the constructed function parameter name.
 */
std::string createParName(size_t index, const std::string &name = "") {
  std::stringstream prefix;
  prefix << "f" << index << "." << name;
  return prefix.str();
}
/**
 * Takes an index, a sub index and a name, and constructs a double level
 * (nested) parameter name for use with function ties, etc.
 *
 * @param index    :: the index of the function in the first level.
 * @param subIndex :: the index of the function in the second level.
 * @param name     :: the name of the parameter inside the function.
 *
 * @returns the constructed function parameter name.
 */
std::string createParName(size_t index, size_t subIndex,
                          const std::string &name = "") {
  std::stringstream prefix;
  prefix << "f" << index << ".f" << subIndex << "." << name;
  return prefix.str();
}
}
/**
 * Creates a function to carry out the fitting in the "ConvFit" tab.  The
 * function consists of various sub functions, with the following structure:
 *  +- LinearBackground
 *  +- Convolution
 *      +- Resolution
 *      +- Model (AT LEAST one delta function or one/two lorentzians.)
 *          +- DeltaFunction(yes/no)
 *				+- ProductFunction
 *					|
 *					+- Lorentzian 1(yes/no)
 *					+- Temperature Correction(yes/no)
 *				+- ProductFunction
 *					|
 *					+- Lorentzian 2(yes/no)
 *					+- Temperature Correction(yes/no)
 *				+- ProductFunction
 *					|
 *					+- InelasticDiffSphere(yes/no)
 *					+- Temperature Correction(yes/no)
 *				+- ProductFunction
 *					|
 *					+-
 *InelasticDiffRotDiscreteCircle(yes/no)
 *					+- Temperature Correction(yes/no)
 *
 * @param tieCentres :: whether to tie centres of the two lorentzians.
 *
 * @returns the composite fitting function.
 */
CompositeFunction_sptr ConvFit::createFunction(bool tieCentres) {
  auto conv = boost::dynamic_pointer_cast<CompositeFunction>(
      FunctionFactory::Instance().createFunction("Convolution"));
  CompositeFunction_sptr comp(new CompositeFunction);

  IFunction_sptr func;
  size_t index = 0;

  // -------------------------------------
  // --- Composite / Linear Background ---
  // -------------------------------------
  func = FunctionFactory::Instance().createFunction("LinearBackground");
  comp->addFunction(func);

  // 0 = Fixed Flat, 1 = Fit Flat, 2 = Fit all
  const int bgType = m_uiForm.cbBackground->currentIndex();

  if (bgType == 0 || !m_properties["BGA0"]->subProperties().isEmpty()) {
    comp->tie("f0.A0", m_properties["BGA0"]->valueText().toStdString());
  } else {
    func->setParameter("A0", m_properties["BGA0"]->valueText().toDouble());
  if (bgType != 2) {
    comp->tie("f0.A1", "0.0");
  } else {
    if (!m_properties["BGA1"]->subProperties().isEmpty()) {
      comp->tie("f0.A1", m_properties["BGA1"]->valueText().toStdString());
    } else {
      func->setParameter("A1", m_properties["BGA1"]->valueText().toDouble());
  // --------------------------------------------
  // --- Composite / Convolution / Resolution ---
  // --------------------------------------------
  func = FunctionFactory::Instance().createFunction("Resolution");
  conv->addFunction(func);
  // add resolution file
  IFunction::Attribute attr("__ConvFit_Resolution");
  func->setAttribute("Workspace", attr);
  // --------------------------------------------------------
  // --- Composite / Convolution / Model / Delta Function ---
  // --------------------------------------------------------
  CompositeFunction_sptr model(new CompositeFunction);
  bool useDeltaFunc = m_blnManager->value(m_properties["UseDeltaFunc"]);
  if (useDeltaFunc) {
    func = FunctionFactory::Instance().createFunction("DeltaFunction");
    index = model->addFunction(func);
    std::string parName = createParName(index);
    populateFunction(func, model, m_properties["DeltaFunction"], parName,
                     false);
  }
  // ------------------------------------------------------------
  // --- Composite / Convolution / Model / Temperature Factor ---
  // ------------------------------------------------------------

  // create temperature correction function to multiply with the lorentzians
  IFunction_sptr tempFunc;
  QString temperature = m_uiForm.leTempCorrection->text();
  bool useTempCorrection =
      (!temperature.isEmpty() && m_uiForm.ckTempCorrection->isChecked());

  // -----------------------------------------------------
  // --- Composite / Convolution / Model / Lorentzians ---
  // -----------------------------------------------------
  std::string prefix1;
  std::string prefix2;

  int fitTypeIndex = m_uiForm.cbFitType->currentIndex();
    size_t subIndex = 0;
    auto product = boost::dynamic_pointer_cast<CompositeFunction>(
        FunctionFactory::Instance().createFunction("ProductFunction"));
    if (useTempCorrection) {
      createTemperatureCorrection(product);
    // Add 1st Lorentzian

    // if temperature not included then product is lorentzian * 1
    // create product function for temp * lorentzian
    std::string functionName = m_uiForm.cbFitType->currentText().toStdString();

    if (fitTypeIndex == 1 || fitTypeIndex == 2) {
      functionName = "Lorentzian";
    }
    func = FunctionFactory::Instance().createFunction(functionName);
    subIndex = product->addFunction(func);
    index = model->addFunction(product);
    prefix1 = createParName(index, subIndex);

    populateFunction(func, model, m_properties["FitFunction1"], prefix1, false);
    // Add 2nd Lorentzian
    if (fitTypeIndex == 2) {
      // if temperature not included then product is lorentzian * 1
      // create product function for temp * lorentzian
      auto product = boost::dynamic_pointer_cast<CompositeFunction>(
          FunctionFactory::Instance().createFunction("ProductFunction"));

      if (useTempCorrection) {
        createTemperatureCorrection(product);
      }

      func = FunctionFactory::Instance().createFunction(functionName);
      subIndex = product->addFunction(func);
      index = model->addFunction(product);
      prefix2 = createParName(index, subIndex);

      populateFunction(func, model, m_properties["FitFunction2"], prefix2,
                       false);
    }
  conv->addFunction(model);
  comp->addFunction(conv);
  // Tie PeakCentres together
  if (tieCentres) {
    std::string tieL = prefix1 + "PeakCentre";
    std::string tieR = prefix2 + "PeakCentre";
    model->tie(tieL, tieR);
  comp->applyTies();
  return comp;
}
/**
 * Creates the correction for the temperature
 */
void ConvFit::createTemperatureCorrection(CompositeFunction_sptr product) {
  // create temperature correction function to multiply with the lorentzians
  IFunction_sptr tempFunc;
  QString temperature = m_uiForm.leTempCorrection->text();

  // create user function for the exponential correction
  // (x*temp) / 1-exp(-(x*temp))
  tempFunc = FunctionFactory::Instance().createFunction("UserFunction");
  // 11.606 is the conversion factor from meV to K
  std::string formula = "((x*11.606)/Temp) / (1 - exp(-((x*11.606)/Temp)))";
  IFunction::Attribute att(formula);
  tempFunc->setAttribute("Formula", att);
  tempFunc->setParameter("Temp", temperature.toDouble());

  product->addFunction(tempFunc);
  product->tie("f0.Temp", temperature.toStdString());
  product->applyTies();
}
/**
 * Obtains the instrument resolution from the provided workspace
 * @param workspaceName The name of the workspaces which holds the instrument
 * resolution
 * @return The resolution of the instrument. returns 0 if no resolution data
 * could be found
 */
double ConvFit::getInstrumentResolution(std::string workspaceName) {
  using namespace Mantid::API;

  double resolution = 0.0;
  try {
    Mantid::Geometry::Instrument_const_sptr inst =
        AnalysisDataService::Instance()
            .retrieveWS<MatrixWorkspace>(workspaceName)
            ->getInstrument();
    std::vector<std::string> analysers = inst->getStringParameter("analyser");
    if (analysers.empty()) {
      g_log.warning("Could not load instrument resolution from parameter file");
    std::string analyser = analysers[0];
    std::string idfDirectory =
        Mantid::Kernel::ConfigService::Instance().getString(
            "instrumentDefinition.directory");

    // If the analyser component is not already in the data file then load it
    // from the parameter file
    if (inst->getComponentByName(analyser)
            ->getNumberParameter("resolution")
            .size() == 0) {
      std::string reflection = inst->getStringParameter("reflection")[0];

      IAlgorithm_sptr loadParamFile =
          AlgorithmManager::Instance().create("LoadParameterFile");
      loadParamFile->initialize();
      loadParamFile->setProperty("Workspace", workspaceName);
      loadParamFile->setProperty(
          "Filename", idfDirectory + inst->getName() + "_" + analyser + "_" +
                          reflection + "_Parameters.xml");
      loadParamFile->execute();

      if (!loadParamFile->isExecuted()) {
        g_log.warning("Could not load parameter file, ensure instrument "
                      "directory is in data search paths.");
        return 0.0;
      }
      inst = AnalysisDataService::Instance()
                 .retrieveWS<MatrixWorkspace>(workspaceName)
                 ->getInstrument();
    }
    resolution =
        inst->getComponentByName(analyser)->getNumberParameter("resolution")[0];
  } catch (Mantid::Kernel::Exception::NotFoundError &e) {
    UNUSED_ARG(e);
    g_log.warning("Could not load instrument resolution from parameter file");
    resolution = 0.0;
/**
 * Intialises the property values for any of the fit type
 * @param propName The name of the property group
 * @return The popuated property group representing a fit type
 */
QtProperty *ConvFit::createFitType(const QString &propName) {
  QtProperty *fitTypeGroup = m_grpManager->addProperty(propName);
  QString cbName = propName;
  if (propName.compare("Lorentzian 1") == 0) {
    cbName = "One Lorentzian";
  if (propName.compare("Lorentzian 2") == 0) {
    cbName = "Two Lorentzians";
  }
  auto params = getFunctionParameters(cbName);
  for (auto it = params.begin(); it != params.end(); ++it) {
    QString paramName = propName + "." + *it;
    m_properties[paramName] = m_dblManager->addProperty(*it);
    m_dblManager->setDecimals(m_properties[paramName], NUM_DECIMALS);
    if (QString(*it).compare("FWHM") == 0) {
      m_dblManager->setValue(m_properties[paramName], 0.02);
    }
    fitTypeGroup->addSubProperty(m_properties[paramName]);
 * Populates the properties of a function with given values
 * @param func The function currently being added to the composite
 * @param comp A composite function of the previously called functions
 * @param group The QtProperty representing the fit type
 * @param pref The index of the functions eg. (f0.f1)
 * @param tie Bool to state if parameters are to be tied together
 */
void ConvFit::populateFunction(IFunction_sptr func, IFunction_sptr comp,
                               QtProperty *group, const std::string &pref,
                               bool tie) {
  // Get subproperties of group and apply them as parameters on the function
  // object
  QList<QtProperty *> props = group->subProperties();

  for (int i = 0; i < props.size(); i++) {
    if (tie || !props[i]->subProperties().isEmpty()) {
      std::string name = pref + props[i]->propertyName().toStdString();
      std::string value = props[i]->valueText().toStdString();
      comp->tie(name, value);
    } else {
      std::string propName = props[i]->propertyName().toStdString();
      double propValue = props[i]->valueText().toDouble();
      if (propValue) {
        if (func->hasAttribute(propName))
          func->setAttributeValue(propName, propValue);
        else
          func->setParameter(propName, propValue);
/**
 * Generate a string to describe the fit type selected by the user.
 * Used when naming the resultant workspaces.
 *
 * Assertions used to guard against any future changes that dont take
 * workspace naming into account.
 *
 * @returns the generated QString.
 */
QString ConvFit::fitTypeString() const {
  QString fitType("");

  if (m_blnManager->value(m_properties["UseDeltaFunc"]))
    fitType += "Delta";

  fitType += m_fitStrings.at(m_uiForm.cbFitType->currentIndex());
/**
 * Generate a string to describe the background selected by the user.
 * Used when naming the resultant workspaces.
 *
 * Assertions used to guard against any future changes that dont take
 * workspace naming into account.
 *
 * @returns the generated QString.
 */
QString ConvFit::backgroundString() const {
  switch (m_uiForm.cbBackground->currentIndex()) {
  case 0:
    return "FixF_s";
  case 1:
    return "FitF_s";
  case 2:
    return "FitL_s";
  default:
    return "";
/**
 * Generates a string that defines the fitting minimizer based on the user
 * options.
 *
 * @return Minimizer as a string
 */
QString ConvFit::minimizerString(QString outputName) const {
  QString minimizer = "Levenberg-Marquardt";
  if (m_blnManager->value(m_properties["UseFABADA"])) {
    minimizer = "FABADA";
    int chainLength = static_cast<int>(
        m_dblManager->value(m_properties["FABADAChainLength"]));
    minimizer += ",ChainLength=" + QString::number(chainLength);
    double convergenceCriteria =
        m_dblManager->value(m_properties["FABADAConvergenceCriteria"]);
    minimizer += ",ConvergenceCriteria=" + QString::number(convergenceCriteria);
    double jumpAcceptanceRate =
        m_dblManager->value(m_properties["FABADAJumpAcceptanceRate"]);
    minimizer += ",JumpAcceptanceRate=" + QString::number(jumpAcceptanceRate);
    minimizer += ",PDF=" + outputName + "_PDF";
    if (m_blnManager->value(m_properties["OutputFABADAChain"]))
      minimizer += ",Chains=" + outputName + "_Chain";
  }
 * Changes property tree and plot appearance based on Fit Type
 * @param index A reference to the Fit Type (0-9)
 */
void ConvFit::typeSelection(int index) {
  auto hwhmRangeSelector = m_uiForm.ppPlot->getRangeSelector("ConvFitHWHM");

  if (index == 0) {
    hwhmRangeSelector->setVisible(false);
  } else if (index < 3) {
    hwhmRangeSelector->setVisible(true);
  } else {
    hwhmRangeSelector->setVisible(false);
    m_uiForm.ckPlotGuess->setChecked(false);
    m_blnManager->setValue(m_properties["UseDeltaFunc"], false);
  }

  // Disable Plot Guess and Use Delta Function for DiffSphere and
  // DiffRotDiscreteCircle
  m_uiForm.ckPlotGuess->setEnabled(index < 3);
  m_properties["UseDeltaFunc"]->setEnabled(index < 3);
/**
 * Add/Remove sub property 'BGA1' from background based on Background type
 * @param index A reference to the Background type
void ConvFit::bgTypeSelection(int index) {
  if (index == 2) {
    m_properties["LinearBackground"]->addSubProperty(m_properties["BGA1"]);
  } else {
    m_properties["LinearBackground"]->removeSubProperty(m_properties["BGA1"]);
/**
 * Updates the plot in the gui window
void ConvFit::updatePlot() {
  using Mantid::Kernel::Exception::NotFoundError;
  if (!m_cfInputWS) {
    g_log.error("No workspace loaded, cannot create preview plot.");
    return;
  bool plotGuess = m_uiForm.ckPlotGuess->isChecked();
  m_uiForm.ckPlotGuess->setChecked(false);
  int specNo = m_uiForm.spPlotSpectrum->text().toInt();
  m_uiForm.ppPlot->clear();
  m_uiForm.ppPlot->addSpectrum("Sample", m_cfInputWS, specNo);
  try {
    const QPair<double, double> curveRange =
        m_uiForm.ppPlot->getCurveRange("Sample");
    const std::pair<double, double> range(curveRange.first, curveRange.second);
    m_uiForm.ppPlot->getRangeSelector("ConvFitRange")
        ->setRange(range.first, range.second);
    m_uiForm.ckPlotGuess->setChecked(plotGuess);
  } catch (std::invalid_argument &exc) {
    showMessageBox(exc.what());
  // Default FWHM to resolution of instrument
  double resolution = getInstrumentResolution(m_cfInputWSName.toStdString());
  if (resolution > 0) {
    m_dblManager->setValue(m_properties["Lorentzian 1.FWHM"], resolution);
    m_dblManager->setValue(m_properties["Lorentzian 2.FWHM"], resolution);
  // If there is a result plot then plot it
  std::string groupName = m_baseName.toStdString() + "_Workspaces";
  if (AnalysisDataService::Instance().doesExist(groupName)) {
    WorkspaceGroup_sptr outputGroup =
        AnalysisDataService::Instance().retrieveWS<WorkspaceGroup>(groupName);
    if (specNo - m_runMin >= static_cast<int>(outputGroup->size()))
    if ((specNo - m_runMin) >= 0) {
      MatrixWorkspace_sptr ws = boost::dynamic_pointer_cast<MatrixWorkspace>(
		  outputGroup->getItem(specNo- m_runMin));
      if (ws) {