Commit 59f039ad authored by Roman Tolchenov's avatar Roman Tolchenov
Browse files

Added ties to the fitting user interface. re #1063

parent 29576ce4
......@@ -131,6 +131,12 @@ public:
void removeFunction(int i, bool del=true);
/// Replace a function
void replaceFunction(int i,IFunction* f);
/// Get the function index
int functionIndex(int i)const;
/// Get the function index
int functionIndexActive(int i)const;
/// Returns the name of parameter i as it declared in its function
std::string parameterLocalName(int i)const;
protected:
/// Function initialization. Declare function parameters in this method.
......@@ -142,10 +148,6 @@ protected:
private:
/// Get the function index
int functionIndex(int i)const;
/// Get the function index
int functionIndexActive(int i)const;
/// Extract function index and parameter name from a variable name
static void parseName(const std::string& varName,int& index, std::string& name);
......
......@@ -613,6 +613,16 @@ void CompositeFunction::parseName(const std::string& varName,int& index, std::st
}
}
/** Returns the name of parameter i as it declared in its function
* @param i The parameter index
* @return The pure parameter name (without the function identifier f#.)
*/
std::string CompositeFunction::parameterLocalName(int i)const
{
int iFun = functionIndex(i);
return m_functions[ iFun ]->parameterName(i - m_paramOffsets[iFun]);
}
/** Initialize the function providing it the workspace
* @param workspace The shared pointer to a workspace to which the function will be fitted
* @param spec The number of a spectrum for fitting
......
......@@ -70,7 +70,7 @@ void Gaussian::setActiveParameter(int i,double value)
{
int j = indexOfActive(i);
if (nameOfActive(j) == "Sigma")
if (parameterName(j) == "Sigma")
parameter(j) = sqrt(1./value);
else
parameter(j) = value;
......@@ -80,7 +80,7 @@ double Gaussian::activeParameter(int i)const
{
int j = indexOfActive(i);
if (nameOfActive(j) == "Sigma")
if (parameterName(j) == "Sigma")
return 1./pow(parameter(j),2);
else
return parameter(j);
......
......@@ -397,6 +397,7 @@ HEADERS += src/ApplicationWindow.h \
src/Mantid/UserFitFunctionDialog.h \
src/Mantid/FitPropertyBrowser.h \
src/Mantid/IFunctionWrapper.h \
src/Mantid/FitParameterTie.h \
src/Mantid/InstrumentWidget/GLColor.h \
src/Mantid/InstrumentWidget/GLObject.h \
src/Mantid/InstrumentWidget/GLTrackball.h \
......@@ -571,6 +572,7 @@ SOURCES += src/ApplicationWindow.cpp \
src/Mantid/UserFitFunctionDialog.cpp \
src/Mantid/FitPropertyBrowser.cpp \
src/Mantid/IFunctionWrapper.cpp \
src/Mantid/FitParameterTie.cpp \
src/Mantid/InstrumentWidget/GLColor.cpp \
src/Mantid/InstrumentWidget/GLObject.cpp \
src/Mantid/InstrumentWidget/GLTrackball.cpp \
......
#include "FitParameterTie.h"
#include "MantidAPI/CompositeFunction.h"
#include <QRegExp>
#include <stdexcept>
#include <iostream>
/// Constructor
FitParameterTie::FitParameterTie(boost::shared_ptr<Mantid::API::CompositeFunction> cf)
:m_compositeFunction(cf),m_prop(0)
{}
/// Destructor
FitParameterTie::~FitParameterTie()
{
if (m_prop)
{
//delete m_prop;
}
}
/** Set the tying expression. The function names (f0,f1,f2,...) are changed to
* placeholders (#0,#1,#2) to make it easier to edit afterwards.
* @param estr The tying expression , e.g. "f1.Sigma = 2*f0.Sigma + 1"
*/
void FitParameterTie::set(const QString& estr)
{
int ieq = estr.indexOf('=');
if (ieq < 0)
{
throw std::invalid_argument("The tie expression doesn't contain the tied parameter.\n"
"Syntax: <tied_name> = <tying_expression>");
}
if (ieq == estr.size())
{
throw std::invalid_argument("The tying expression is missing.\n"
"Syntax: <tied_name> = <tying_expression>");
}
if (estr.mid(ieq+1).trimmed().isEmpty())
{
throw std::invalid_argument("The tying expression is missing.\n"
"Syntax: <tied_name> = <tying_expression>");
}
QString parName = estr.left(ieq).trimmed();
// rx matches function identifiers in the parameter names and captures the function index:
// for f12.Sigma rx.cap(1).toInt() returns 12
QRegExp rx("\\bf(\\d+)\\.");
if (rx.indexIn(parName) < 0)
{
throw std::invalid_argument("Parameter names must contain function identifiers:\n"
"e.g. f0.Sigma, f5.HWHM");
}
m_expr = estr;
for(int i=rx.indexIn(m_expr);i>=0;)
{
int iFun = rx.cap(1).toInt();
int j = m_iFunctions.indexOf(iFun);
if (j < 0)
{
j = m_iFunctions.size();
m_iFunctions.append(iFun);
}
QString s = "#"+QString::number(j)+".";
m_expr.replace(rx.pos(),rx.cap().size(),s);
i=rx.indexIn(m_expr,i+rx.cap().size());
}
}
/// The tying expression
QString FitParameterTie::expr()const
{
QString str = m_expr;
for(int j=0;j<m_iFunctions.size();j++)
{
QString ph = "#"+QString::number(j);
QString fi = "f"+QString::number(m_iFunctions[j]);
str.replace(ph,fi);
}
return str;
}
/// The parameter name
QString FitParameterTie::parName()const
{
QString str = m_expr.left(m_expr.indexOf('=')).trimmed();
for(int j=0;j<m_iFunctions.size();j++)
{
QString ph = "#"+QString::number(j);
QString fi = "f"+QString::number(m_iFunctions[j]);
str.replace(ph,fi);
}
return str;
}
/// Returns the right-hand side of the expression
QString FitParameterTie::exprRHS()const
{
QString ex = expr();
int ieq = ex.indexOf('=');
if (ieq<0)
{
return ex;
}
if (ieq==ex.size()-1)
{
return "";
}
return ex.mid(ieq+1);
}
/**
* When a new function is added the function indeces in the tying expression must
* be changed.
* @param i The index at wich the function is inserted. All old indeces starting
* from i (inclusive) must be incremented.
*/
void FitParameterTie::functionInserted(int i)
{
for(int j=0;j<m_iFunctions.size();j++)
{
if (m_iFunctions[j] >= i)
{
m_iFunctions[j]++;
}
}
}
/**
* When a function is deleted the function indeces in the tying expression must
* be changed or the tie may become invalid if the deleted function is used in the tie.
* @param i The index of the deleted function. All old indeces starting
* from i+1 must be decremented.
* @return true if the tie remains valid and false otherwise.
*/
bool FitParameterTie::functionDeleted(int i)
{
for(int j=0;j<m_iFunctions.size();j++)
{
if (m_iFunctions[j] == i)
{
return false;
}
if (m_iFunctions[j] > i)
{
m_iFunctions[j]--;
}
}
return true;
}
#ifndef FITPARAMETERTIE_H
#define FITPARAMETERTIE_H
#include <QString>
#include <QList>
#include <boost/shared_ptr.hpp>
namespace Mantid
{
namespace API
{
class CompositeFunction;
}
}
class QtProperty;
/**
* Class FitParameterTie is for editing parameter ties in Mantid functions.
*/
class FitParameterTie
{
public:
/// Constructor
FitParameterTie(boost::shared_ptr<Mantid::API::CompositeFunction> cf);
/// Destructor
~FitParameterTie();
/// Set the tying expression, e.g. "f1.Sigma = 2*f0.Sigma + 1"
void set(const QString& estr);
/// The tying expression
QString expr()const;
/// The parameter name
QString parName()const;
/// Returns the right-hand side of the expression
QString exprRHS()const;
/// Mofifies the function indeces in response to insertion of a new function into
/// the composite function
void functionInserted(int i);
/// Mofifies the function indeces in response to deletion of a function from
/// the composite function
bool functionDeleted(int i);
/// Set property
void setProperty(QtProperty* prop){m_prop=prop;}
/// Get property
QtProperty* getProperty()const{return m_prop;}
private:
/// The tying expression
QString m_expr;
/// Function indeces used in the expression
QList<int> m_iFunctions;
/// A copy of the edited function
boost::shared_ptr<Mantid::API::CompositeFunction> m_compositeFunction;
/// The property
QtProperty* m_prop;
};
#endif /* FITPARAMETERTIE_H */
......@@ -104,12 +104,15 @@ m_appWindow((ApplicationWindow*)parent),m_guessOutputName(true),m_changeSlotsEna
QPushButton* btnClear = new QPushButton("Clear all");
connect(btnClear,SIGNAL(clicked()),this,SLOT(clear()));
m_tip = new QLabel("",w);
buttonsLayout->addWidget(m_btnFit);
buttonsLayout->addWidget(m_btnUnFit);
buttonsLayout->addWidget(btnClear);
buttonsLayout->addStretch();
layout->addLayout(buttonsLayout);
layout->addWidget(m_tip);
layout->addWidget(m_browser);
setWidget(w);
......@@ -134,7 +137,7 @@ void FitPropertyBrowser::popupMenu(const QPoint &pos)
QMenu *menu = new QMenu(m_appWindow);
if (ci->property() == m_functionsGroup)
{
QAction *action = new QAction("Add new function",this);
QAction *action = new QAction("Add function",this);
connect(action,SIGNAL(triggered()),this,SLOT(addFunction()));
menu->addAction(action);
menu->addSeparator();
......@@ -165,6 +168,29 @@ void FitPropertyBrowser::popupMenu(const QPoint &pos)
connect(action,SIGNAL(triggered()),this,SLOT(clear()));
menu->addAction(action);
if (ci->property()->propertyName() == "Tie")
{
menu->addSeparator();
action = new QAction("Delete",this);
connect(action,SIGNAL(triggered()),this,SLOT(deleteTie()));
menu->addAction(action);
}
else if (count() > 0 && !hasTie(ci->property()))
{
menu->addSeparator();
action = new QAction("Add tie",this);
connect(action,SIGNAL(triggered()),this,SLOT(addTie()));
menu->addAction(action);
}
else if (hasTie(ci->property()))
{
menu->addSeparator();
action = new QAction("Delete tie",this);
connect(action,SIGNAL(triggered()),this,SLOT(deleteTie()));
menu->addAction(action);
}
menu->popup(QCursor::pos());
}
......@@ -220,6 +246,8 @@ void FitPropertyBrowser::addFunction(const std::string& fnName)
m_functionItems.append(fnItem);
selectFunction(index());
updateParameters();
m_changeSlotsEnabled = true;
setFitEnabled(true);
......@@ -242,6 +270,7 @@ void FitPropertyBrowser::replaceFunction(int i,const std::string& fnName)
pf->setHeight(peakFunction(i)->height());
pf->setWidth(peakFunction(i)->width());
}
removeTiesWithFunction(i);// do it before replaceFunction
m_compositeFunction->replaceFunction(i,f);
QtBrowserItem* fnItem = m_functionItems[i];
fnItem->property()->setPropertyName(functionName(i));
......@@ -252,7 +281,7 @@ void FitPropertyBrowser::replaceFunction(int i,const std::string& fnName)
for(int j=f->nParams()+1;j<subs.size();j++)
{
fnItem->property()->removeSubProperty(subs[j]);
delete subs[j];
delete subs[j]; // ?
}
}
else if (subs.size()-1 < f->nParams())
......@@ -270,6 +299,7 @@ void FitPropertyBrowser::replaceFunction(int i,const std::string& fnName)
m_doubleManager->setValue(subs[j+1],f->parameter(j));
}
updateParameters();
}
/** Remove a function
......@@ -279,12 +309,13 @@ void FitPropertyBrowser::removeFunction(int i)
{
if (i < 0 || i >= count()) return;
disableUndo();
removeTiesWithFunction(i);// do it before removeFunction
QtBrowserItem* fnItem = m_functionItems[i];
QList<QtProperty*> subs = fnItem->property()->subProperties();
for(int j=0;j<subs.size();j++)
{
fnItem->property()->removeSubProperty(subs[i]);
delete subs[j];
fnItem->property()->removeSubProperty(subs[j]);
delete subs[j]; // ?
}
QtProperty* fnGroup = fnItem->parent()->property();
fnGroup->removeSubProperty(fnItem->property());
......@@ -301,6 +332,7 @@ void FitPropertyBrowser::removeFunction(int i)
{
setFitEnabled(false);
}
updateParameters();
emit functionRemoved(i);
}
......@@ -514,6 +546,40 @@ void FitPropertyBrowser::stringChanged(QtProperty* prop)
m_guessOutputName = false;
}
}
else if (prop->propertyName() == "Tie")
{
for(int i=0;i<m_ties.size();i++)
{
if (prop == m_ties[i].getProperty())
{
QString estr = m_stringManager->value(prop);
// Make sure the tied parameter is right and the property keeps only the right-hand side formula
int j = estr.indexOf('=');
if (j == estr.size())
{
m_appWindow->mantidUI->showCritical("Tie expression is missing");
m_stringManager->setValue(prop,"");
return;
}
if (j >= 0)
{
estr.remove(0,j+1);
m_stringManager->setValue(prop,estr);
return;
}
try
{
estr.prepend(m_ties[i].parName()+"=");
m_ties[i].set(estr);
}
catch(std::exception& e)
{
//QString msg = "Error in tie \""+estr+"\":\n\n"+QString::fromAscii(e.what());
//m_stringManager->setValue(prop,"");
}
}
}
}
}
// Centre of the current peak
......@@ -636,7 +702,7 @@ void FitPropertyBrowser::setIndex(int i)const
m_index = -1;
return;
}
if (i < 0 || i >= count()) return;
if (i < -1 || i >= count()) return;
m_index = i;
emit indexChanged(i);
}
......@@ -680,6 +746,16 @@ void FitPropertyBrowser::fit()
alg->setProperty("EndX",endX());
alg->setPropertyValue("Output",outputName());
alg->setPropertyValue("Function",*m_compositeFunction);
QString tiesStr;
for(int i=0;i<m_ties.size();i++)
{
tiesStr += m_ties[i].expr();
if (i!=m_ties.size()-1)
{
tiesStr += ",";
}
}
alg->setPropertyValue("Ties",tiesStr.toStdString());
observeFinish(alg);
alg->executeAsync();
}
......@@ -863,11 +939,41 @@ QtBrowserItem* FitPropertyBrowser::findItem(QtBrowserItem* parent,QtProperty* pr
*/
void FitPropertyBrowser::currentItemChanged(QtBrowserItem * current )
{
if (!current) return;
bool ok = false;
int i = m_functionItems.indexOf(current);
if (i >= 0)
{
ok = true;
}
else if (current == m_fitGroup || current->parent() == m_fitGroup || current->parent()->property() == m_settingsGroup)
{
}
else
{
i = m_functionItems.indexOf(current->parent());
if (i >= 0)
{
ok = true;
}
else
{
i = m_functionItems.indexOf(current->parent()->parent());
if (i >= 0)
{
ok = true;
}
}
}
if (ok)
{
setIndex(i);
}
else
{
setIndex(-1);
}
}
/// Update the function parameter properties
......@@ -876,12 +982,24 @@ void FitPropertyBrowser::updateParameters()
for(int i=0;i<m_functionItems.size();i++)
{
QtBrowserItem* fnItem = m_functionItems[i];
QList<QtProperty*> subs = fnItem->property()->subProperties();
QList<QtProperty*> paramProps = fnItem->property()->subProperties();
Mantid::API::IFunction* f = m_compositeFunction->getFunction(i);
for(int j=1;j<subs.size();j++)
for(int j=1;j<paramProps.size();j++)
{
double v = f->parameter(j-1);
m_doubleManager->setValue(subs[j],v);
m_doubleManager->setValue(paramProps[j],v);
QList<QtProperty*> tieProps = paramProps[j]->subProperties();
for(int k=0;k<tieProps.size();k++)
{
if (tieProps[k]->propertyName() == "Tie")
{
int it = indexOfTie(tieProps[k]);
if (it >= 0)
{
m_stringManager->setValue(tieProps[k],m_ties[it].exprRHS());
}
}
}
}
}
}
......@@ -900,12 +1018,13 @@ void FitPropertyBrowser::clear()
for(int j=0;j<subs.size();j++)
{
fnProp->removeSubProperty(subs[j]);
delete subs[j];
delete subs[j]; // ?
}
m_functionsGroup->removeSubProperty(fnProp);
delete fnProp;
delete fnProp; // ?
}
m_functionItems.clear();
m_ties.clear();
createCompositeFunction();
emit functionCleared();
}
......@@ -984,3 +1103,197 @@ bool FitPropertyBrowser::isFitEnabled()const
{
return m_btnFit->isEnabled();
}
/** Adds a tie
* @param tstr The expression, e.g. "f1.Sigma= f0.Height/2"
*/
void FitPropertyBrowser::addTie(const QString& tstr)
{
m_ties.push_back(FitParameterTie(m_compositeFunction));
FitParameterTie& tie = m_ties.back();
try
{
tie.set(tstr);
int iPar = compositeFunction()->parameterIndex(tie.parName().toStdString());
int iFun = compositeFunction()->functionIndex(iPar);
QtBrowserItem* fnItem = m_functionItems[iFun];
QtProperty* fnProp = fnItem->property();
std::string parName = compositeFunction()->parameterLocalName(iPar);
QtProperty* tieProp = m_stringManager->addProperty( "Tie" );
tie.setProperty(tieProp);
m_stringManager->setValue(tieProp,tie.expr());
int iPar1 = function(iFun)->parameterIndex(parName)+1;
QtProperty* parProp = fnProp->subProperties()[iPar1];
parProp->addSubProperty(tieProp);
m_browser->setExpanded(fnItem->children()[iPar1],false);
}
catch(std::exception& e)
{
QString msg = "Error in a tie:\n\n"+QString(e.what())+"\n";
m_appWindow->mantidUI->showCritical(msg);
}
}
/** Adds a tie
* @param i The function index
* @param parProp The property of the tied parameter
*/
void FitPropertyBrowser::addTie(int i,QtProperty* parProp,const QString& tieExpr)
{
m_ties.push_back(FitParameterTie(m_compositeFunction));
FitParameterTie& tie = m_ties.back();
QtProperty* tieProp = m_stringManager->addProperty( "Tie" );
tie.setProperty(tieProp);
double value = m_doubleManager->value(parProp);
tie.set(tieExpr);
parProp->addSubProperty(tieProp);
//m_browser->setBackgroundColor(findItem(m_functionItems[i],parProp),QColor(Qt::yellow));
}