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

#include "MantidAPI/AlgorithmManager.h"
#include "MantidAPI/FunctionFactory.h"
#include "MantidAPI/FunctionDomain1D.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) :
    IDATab(parent),
    m_stringManager(NULL), m_cfTree(NULL),
    m_fixedProps(),
    m_cfInputWS(), m_cfInputWSName(),
    m_confitResFileType()
Dan Nixon's avatar
Dan Nixon committed
    m_uiForm.setupUi(parent);
  void ConvFit::setup()
  {
    // Create Property Managers
    m_stringManager = new QtStringPropertyManager();

    // Create TreeProperty Widget
    m_cfTree = new QtTreePropertyBrowser();
Dan Nixon's avatar
Dan Nixon committed
    m_uiForm.properties->addWidget(m_cfTree);

    // add factories to managers
Dan Nixon's avatar
Dan Nixon committed
    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
Dan Nixon's avatar
Dan Nixon committed
    m_properties["Convolve"] = m_blnManager->addProperty("Convolve");
    m_cfTree->addProperty(m_properties["Convolve"]);
    m_blnManager->setValue(m_properties["Convolve"], true);

Dan Nixon's avatar
Dan Nixon committed
    // 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);
Dan Nixon's avatar
Dan Nixon committed
    m_dblManager->setValue(m_properties["FABADAChainLength"], 10000);
    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"]);
    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"] = createLorentzian("Lorentzian 1");
    m_properties["Lorentzian2"] = createLorentzian("Lorentzian 2");
    m_properties["DiffSphere"] = createDiffSphere("Diffusion Sphere");
    m_properties["DiffRotDiscreteCircle"] = createDiffRotDiscreteCircle("Diffusion Circle");
Dan Nixon's avatar
Dan Nixon committed
    m_uiForm.leTempCorrection->setValidator(new QDoubleValidator(m_parentWidget));
    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)));
Dan Nixon's avatar
Dan Nixon committed
    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);
Dan Nixon's avatar
Dan Nixon committed
    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()));
Dan Nixon's avatar
Dan Nixon committed
    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()));

Dan Nixon's avatar
Dan Nixon committed
    connect(m_uiForm.spSpectraMin, SIGNAL(valueChanged(int)), this, SLOT(specMinChanged(int)));
    connect(m_uiForm.spSpectraMax, SIGNAL(valueChanged(int)), this, SLOT(specMaxChanged(int)));
Dan Nixon's avatar
Dan Nixon committed
    connect(m_uiForm.cbFitType, SIGNAL(currentIndexChanged(int)), this, SLOT(typeSelection(int)));
    connect(m_uiForm.cbBackground, SIGNAL(currentIndexChanged(int)), this, SLOT(bgTypeSelection(int)));
Dan Nixon's avatar
Dan Nixon committed
    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 &)));
Dan Nixon's avatar
Dan Nixon committed
    connect(m_uiForm.cbFitType,SIGNAL(currentIndexChanged(QString)),SLOT(showTieCheckbox(QString)));
    showTieCheckbox( m_uiForm.cbFitType->currentText() );

    updatePlotOptions();
    if ( m_cfInputWS == NULL )
      g_log.error("No workspace loaded");
    QString fitType = fitTypeString();
    QString bgType = backgroundString();
    if(fitType == "")
    {
      g_log.error("No fit type defined");
    }

Dan Nixon's avatar
Dan Nixon committed
    bool useTies = m_uiForm.ckTieCentres->isChecked();
    QString ties = (useTies ? "True" : "False");
    CompositeFunction_sptr func = createFunction(useTies);
    std::string function = std::string(func->asString());
    QString stX = m_properties["StartX"]->valueText();
    QString enX = m_properties["EndX"]->valueText();
Dan Nixon's avatar
Dan Nixon committed
    QString specMin = m_uiForm.spSpectraMin->text();
    QString specMax = m_uiForm.spSpectraMax->text();
    int maxIterations = static_cast<int>(m_dblManager->value(m_properties["MaxIterations"]));
    QString pyInput =
      "from IndirectDataAnalysis import confitSeq\n"
      "input = '" + m_cfInputWSName + "'\n"
      "func = r'" + QString::fromStdString(function) + "'\n"
      "startx = " + stX + "\n"
      "endx = " + enX + "\n"
Dan Nixon's avatar
Dan Nixon committed
      "plot = '" + m_uiForm.cbPlotType->currentText() + "'\n"
      "ties = " + ties + "\n"
      "specMin = " + specMin + "\n"
      "specMax = " + specMax + "\n"
      "max_iterations = " + QString::number(maxIterations) + "\n"
      "minimizer = '" + minimizerString("$outputname_$wsindex") + "'\n"
Dan Nixon's avatar
Dan Nixon committed
      "save = " + (m_uiForm.ckSave->isChecked() ? "True\n" : "False\n");
Dan Nixon's avatar
Dan Nixon committed
    if ( m_blnManager->value(m_properties["Convolve"]) ) pyInput += "convolve = True\n";
    else pyInput += "convolve = False\n";

Dan Nixon's avatar
Dan Nixon committed
    QString temperature = m_uiForm.leTempCorrection->text();
    bool useTempCorrection = (!temperature.isEmpty() && m_uiForm.ckTempCorrection->isChecked());
    if ( useTempCorrection )
      pyInput += "temp=" + temperature + "\n";
      pyInput += "temp=None\n";
      "bg = '" + bgType + "'\n"
      "ftype = '" + fitType + "'\n"
      "confitSeq(input, func, startx, endx, ftype, bg, temp, specMin, specMax, convolve, max_iterations=max_iterations, minimizer=minimizer, Plot=plot, Save=save)\n";
    QString pyOutput = runPythonCode(pyInput);

    // Set the result workspace for Python script export
    QString inputWsName = QString::fromStdString(m_cfInputWS->getName());
    QString resultWsName = inputWsName.left(inputWsName.lastIndexOf("_")) + "_conv_" + fitType + bgType + specMin + "_to_" + specMax + "_Workspaces";
    m_pythonExportWsName = resultWsName.toStdString();
  /**
   * Validates the user's inputs in the ConvFit tab.
   */
  bool ConvFit::validate()
Dan Nixon's avatar
Dan Nixon committed
    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.)
Dan Nixon's avatar
Dan Nixon committed
    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();
  }

  void ConvFit::loadSettings(const QSettings & settings)
  {
Dan Nixon's avatar
Dan Nixon committed
    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;

Dan Nixon's avatar
Dan Nixon committed
    m_uiForm.spPlotSpectrum->setMaximum(maxSpecIndex);
    m_uiForm.spPlotSpectrum->setMinimum(0);
    m_uiForm.spPlotSpectrum->setValue(0);
Dan Nixon's avatar
Dan Nixon committed
    m_uiForm.spSpectraMin->setMaximum(maxSpecIndex);
    m_uiForm.spSpectraMin->setMinimum(0);
Dan Nixon's avatar
Dan Nixon committed
    m_uiForm.spSpectraMax->setMaximum(maxSpecIndex);
    m_uiForm.spSpectraMax->setMinimum(0);
    m_uiForm.spSpectraMax->setValue(maxSpecIndex);
    updatePlot();
  /**
   * 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:
   *
   * Composite
   *  |
   *  +-- LinearBackground
   *  +-- Convolution
   *      |
   *      +-- Resolution
   *      +-- Model (AT LEAST one delta function or one/two lorentzians.)
   *          |
   *          +-- DeltaFunction (yes/no)
Dan Nixon's avatar
Dan Nixon committed
   *					+-- ProductFunction
   *							|
   *							+-- Lorentzian 1 (yes/no)
Dan Nixon's avatar
Dan Nixon committed
   *							+-- Temperature Correction (yes/no)
   *					+-- ProductFunction
   *							|
   *							+-- Lorentzian 2 (yes/no)
Dan Nixon's avatar
Dan Nixon committed
   *							+-- 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);
Dan Nixon's avatar
Dan Nixon committed
    const int bgType = m_uiForm.cbBackground->currentIndex(); // 0 = Fixed Flat, 1 = Fit Flat, 2 = Fit all
    if ( bgType == 0 || ! m_properties["BGA0"]->subProperties().isEmpty() )
      comp->tie("f0.A0", m_properties["BGA0"]->valueText().toStdString() );
      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);
    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");
Dan Nixon's avatar
Dan Nixon committed
      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;
Dan Nixon's avatar
Dan Nixon committed
    QString temperature = m_uiForm.leTempCorrection->text();
    bool useTempCorrection = (!temperature.isEmpty() && m_uiForm.ckTempCorrection->isChecked());
    // -----------------------------------------------------
    // --- Composite / Convolution / Model / Lorentzians ---
    // -----------------------------------------------------
    std::string prefix1;
    std::string prefix2;
Dan Nixon's avatar
Dan Nixon committed
    int fitTypeIndex = m_uiForm.cbFitType->currentIndex();
    // Add 1st Lorentzian
    if(fitTypeIndex == 1 || 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("Lorentzian");
      subIndex = product->addFunction(func);
      index = model->addFunction(product);
      prefix1 = createParName(index, subIndex);
      populateFunction(func, model, m_properties["Lorentzian1"], 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("Lorentzian");
      subIndex = product->addFunction(func);
      index = model->addFunction(product);
      prefix2 = createParName(index, subIndex);
      populateFunction(func, model, m_properties["Lorentzian2"], prefix2, false);
    // -------------------------------------------------------------
    // --- Composite / Convolution / Model / InelasticDiffSphere ---
    // -------------------------------------------------------------
    if(fitTypeIndex == 3)
    {
      auto product = boost::dynamic_pointer_cast<CompositeFunction>(FunctionFactory::Instance().createFunction("ProductFunction"));

      if(useTempCorrection)
      {
        createTemperatureCorrection(product);
      }

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

      populateFunction(func, model, m_properties["DiffSphere"], prefix2, false);
    }

    // ------------------------------------------------------------------------
    // --- Composite / Convolution / Model / InelasticDiffRotDiscreteCircle ---
    // ------------------------------------------------------------------------
    if(fitTypeIndex == 4)
    {
      auto product = boost::dynamic_pointer_cast<CompositeFunction>(FunctionFactory::Instance().createFunction("ProductFunction"));

      if(useTempCorrection)
      {
        createTemperatureCorrection(product);
      }

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

      populateFunction(func, model, m_properties["DiffRotDiscreteCircle"], 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);
  void ConvFit::createTemperatureCorrection(CompositeFunction_sptr product)
  {
    //create temperature correction function to multiply with the lorentzians
    IFunction_sptr tempFunc;
Dan Nixon's avatar
Dan Nixon committed
    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();
  double ConvFit::getInstrumentResolution(std::string workspaceName)
  {
    using namespace Mantid::API;

    double resolution = 0.0;
    try
    {
      Mantid::Geometry::Instrument_const_sptr inst =
Dan Nixon's avatar
Dan Nixon committed
        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");
        return 0.0;
      }

      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 the laod 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.");
        inst = AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(workspaceName)->getInstrument();
      }

      resolution = inst->getComponentByName(analyser)->getNumberParameter("resolution")[0];
    }
    catch(Mantid::Kernel::Exception::NotFoundError &e)
    {
      g_log.warning("Could not load instrument resolution from parameter file");
      resolution = 0.0;
  QtProperty* ConvFit::createLorentzian(const QString & name)
  {
    QtProperty* lorentzGroup = m_grpManager->addProperty(name);
    m_properties[name+".Amplitude"] = m_dblManager->addProperty("Amplitude");
    // m_dblManager->setRange(m_properties[name+".Amplitude"], 0.0, 1.0); // 0 < Amplitude < 1
    m_properties[name+".PeakCentre"] = m_dblManager->addProperty("PeakCentre");
    m_properties[name+".FWHM"] = m_dblManager->addProperty("FWHM");
    m_dblManager->setDecimals(m_properties[name+".Amplitude"], NUM_DECIMALS);
    m_dblManager->setDecimals(m_properties[name+".PeakCentre"], NUM_DECIMALS);
    m_dblManager->setDecimals(m_properties[name+".FWHM"], NUM_DECIMALS);
    m_dblManager->setValue(m_properties[name+".FWHM"], 0.02);
    lorentzGroup->addSubProperty(m_properties[name+".Amplitude"]);
    lorentzGroup->addSubProperty(m_properties[name+".PeakCentre"]);
    lorentzGroup->addSubProperty(m_properties[name+".FWHM"]);
  QtProperty* ConvFit::createDiffSphere(const QString & name)
  {
    QtProperty* diffSphereGroup = m_grpManager->addProperty(name);

    m_properties[name+".Intensity"] = m_dblManager->addProperty("Intensity");
    m_properties[name+".Radius"] = m_dblManager->addProperty("Radius");
    m_properties[name+".Diffusion"] = m_dblManager->addProperty("Diffusion");
    m_properties[name+".Shift"] = m_dblManager->addProperty("Shift");

    m_dblManager->setDecimals(m_properties[name+".Intensity"], NUM_DECIMALS);
    m_dblManager->setDecimals(m_properties[name+".Radius"], NUM_DECIMALS);
    m_dblManager->setDecimals(m_properties[name+".Diffusion"], NUM_DECIMALS);
    m_dblManager->setDecimals(m_properties[name+".Shift"], NUM_DECIMALS);

    diffSphereGroup->addSubProperty(m_properties[name+".Intensity"]);
    diffSphereGroup->addSubProperty(m_properties[name+".Radius"]);
    diffSphereGroup->addSubProperty(m_properties[name+".Diffusion"]);
    diffSphereGroup->addSubProperty(m_properties[name+".Shift"]);

    return diffSphereGroup;
  }

  QtProperty* ConvFit::createDiffRotDiscreteCircle(const QString & name)
  {
    QtProperty* diffRotDiscreteCircleGroup = m_grpManager->addProperty(name);

    m_properties[name+".N"] = m_dblManager->addProperty("N");
    m_dblManager->setValue(m_properties[name+".N"], 3.0);

    m_properties[name+".Intensity"] = m_dblManager->addProperty("Intensity");
    m_properties[name+".Radius"] = m_dblManager->addProperty("Radius");
    m_properties[name+".Decay"] = m_dblManager->addProperty("Decay");
    m_properties[name+".Shift"] = m_dblManager->addProperty("Shift");

    m_dblManager->setDecimals(m_properties[name+".N"], 0);
    m_dblManager->setDecimals(m_properties[name+".Intensity"], NUM_DECIMALS);
    m_dblManager->setDecimals(m_properties[name+".Radius"], NUM_DECIMALS);
    m_dblManager->setDecimals(m_properties[name+".Decay"], NUM_DECIMALS);
    m_dblManager->setDecimals(m_properties[name+".Shift"], NUM_DECIMALS);

    diffRotDiscreteCircleGroup->addSubProperty(m_properties[name+".N"]);
    diffRotDiscreteCircleGroup->addSubProperty(m_properties[name+".Intensity"]);
    diffRotDiscreteCircleGroup->addSubProperty(m_properties[name+".Radius"]);
    diffRotDiscreteCircleGroup->addSubProperty(m_properties[name+".Decay"]);
    diffRotDiscreteCircleGroup->addSubProperty(m_properties[name+".Shift"]);

    return diffRotDiscreteCircleGroup;
  }

  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"]) )
Dan Nixon's avatar
Dan Nixon committed
    switch ( m_uiForm.cbFitType->currentIndex() )
Dan Nixon's avatar
Dan Nixon committed
      case 0:
        break;
      case 1:
        fitType += "1L"; break;
      case 2:
        fitType += "2L"; break;
      case 3:
        fitType += "DS"; break;
      case 4:
        fitType += "DC"; break;
  /**
   * 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
  {
Dan Nixon's avatar
Dan Nixon committed
    switch ( m_uiForm.cbBackground->currentIndex() )
Dan Nixon's avatar
Dan Nixon committed
      case 0:
        return "FixF_s";
      case 1:
        return "FitF_s";
      case 2:
        return "FitL_s";
      default:
        return "";
Dan Nixon's avatar
Dan Nixon committed
  /**
   * 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";
Dan Nixon's avatar
Dan Nixon committed
    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);
      minimizer += ",PDF=" + outputName + "_PDF";
Dan Nixon's avatar
Dan Nixon committed

      if(m_blnManager->value(m_properties["OutputFABADAChain"]))
        minimizer += ",Chains=" + outputName + "_Chain";
    }

    return minimizer;
  }

  void ConvFit::typeSelection(int index)
  {
    m_cfTree->removeProperty(m_properties["Lorentzian1"]);
    m_cfTree->removeProperty(m_properties["Lorentzian2"]);
    m_cfTree->removeProperty(m_properties["DiffSphere"]);
    m_cfTree->removeProperty(m_properties["DiffRotDiscreteCircle"]);
    auto hwhmRangeSelector = m_uiForm.ppPlot->getRangeSelector("ConvFitHWHM");

Dan Nixon's avatar
Dan Nixon committed
      case 0:
        hwhmRangeSelector->setVisible(false);
Dan Nixon's avatar
Dan Nixon committed
        break;
      case 1:
        m_cfTree->addProperty(m_properties["Lorentzian1"]);
        hwhmRangeSelector->setVisible(true);
Dan Nixon's avatar
Dan Nixon committed
        break;
      case 2:
        m_cfTree->addProperty(m_properties["Lorentzian1"]);
        m_cfTree->addProperty(m_properties["Lorentzian2"]);
        hwhmRangeSelector->setVisible(true);
Dan Nixon's avatar
Dan Nixon committed
        break;
      case 3:
        m_cfTree->addProperty(m_properties["DiffSphere"]);
        hwhmRangeSelector->setVisible(false);
        m_uiForm.ckPlotGuess->setChecked(false);
        m_blnManager->setValue(m_properties["UseDeltaFunc"], false);
      case 4:
        m_cfTree->addProperty(m_properties["DiffRotDiscreteCircle"]);
        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);

    updatePlotOptions();
  void ConvFit::bgTypeSelection(int index)
  {
    if ( index == 2 )
    {
      m_properties["LinearBackground"]->addSubProperty(m_properties["BGA1"]);
      m_properties["LinearBackground"]->removeSubProperty(m_properties["BGA1"]);
  void ConvFit::updatePlot()
    using Mantid::Kernel::Exception::NotFoundError;

    if(!m_cfInputWS)
    {
      g_log.error("No workspace loaded, cannot create preview plot.");
      return;
    }

Dan Nixon's avatar
Dan Nixon committed
    const bool plotGuess = m_uiForm.ckPlotGuess->isChecked();
    m_uiForm.ckPlotGuess->setChecked(false);
Dan Nixon's avatar
Dan Nixon committed
    int specNo = m_uiForm.spPlotSpectrum->text().toInt();
    m_uiForm.ppPlot->clear();
    m_uiForm.ppPlot->addSpectrum("Sample", m_cfInputWS, specNo);

      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);
Dan Nixon's avatar
Dan Nixon committed
      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
    if(AnalysisDataService::Instance().doesExist(m_pythonExportWsName))
    {
      WorkspaceGroup_sptr outputGroup = AnalysisDataService::Instance().retrieveWS<WorkspaceGroup>(m_pythonExportWsName);
      if(specNo >= static_cast<int>(outputGroup->size()))
        return;
      MatrixWorkspace_sptr ws = boost::dynamic_pointer_cast<MatrixWorkspace>(outputGroup->getItem(specNo));
      if(ws)
        m_uiForm.ppPlot->addSpectrum("Fit", ws, 1, Qt::red);
    }
  void ConvFit::plotGuess()
    m_uiForm.ppPlot->removeSpectrum("Guess");

    // Do nothing if there is not a sample and resolution
    if(!(m_uiForm.dsSampleInput->isValid() && m_uiForm.dsResInput->isValid()
          && m_uiForm.ckPlotGuess->isChecked()))
Dan Nixon's avatar
Dan Nixon committed
    bool tieCentres = (m_uiForm.cbFitType->currentIndex() > 1);
    CompositeFunction_sptr function = createFunction(tieCentres);
      updatePlot();
    const size_t binIndexLow = m_cfInputWS->binIndexOf(m_dblManager->value(m_properties["StartX"]));
    const size_t binIndexHigh = m_cfInputWS->binIndexOf(m_dblManager->value(m_properties["EndX"]));
    const size_t nData = binIndexHigh - binIndexLow;

    std::vector<double> inputXData(nData);
    const Mantid::MantidVec& XValues = m_cfInputWS->readX(0);
    const bool isHistogram = m_cfInputWS->isHistogramData();

    for ( size_t i = 0; i < nData; i++ )
    {
      if ( isHistogram )
      {
        inputXData[i] = 0.5 * ( XValues[binIndexLow+i] + XValues[binIndexLow+i+1] );
      }
      else
      {
        inputXData[i] = XValues[binIndexLow+i];
      }
    }