Newer
Older
// Mantid Repository : https://github.com/mantidproject/mantid
//
// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
// NScD Oak Ridge National Laboratory, European Spallation Source,
// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
// SPDX - License - Identifier: GPL - 3.0 +
#include "MantidAPI/FunctionFactory.h"
Roman Tolchenov
committed
#include "MantidAPI/CompositeFunction.h"
Roman Tolchenov
committed
#include "MantidAPI/ConstraintFactory.h"
Roman Tolchenov
committed
#include "MantidAPI/IConstraint.h"
#include "MantidAPI/IFunction1D.h"
#include "MantidAPI/Workspace.h"
#include "MantidKernel/ConfigService.h"
Gigg, Martyn Anthony
committed
#include "MantidKernel/LibraryManager.h"
#include "MantidKernel/StringTokenizer.h"
#include <boost/lexical_cast.hpp>
#include <boost/make_shared.hpp>
#include <sstream>
Roman Tolchenov
committed
namespace Mantid {
namespace API {
FunctionFactoryImpl::FunctionFactoryImpl()
: Kernel::DynamicFactory<IFunction>() {
// we need to make sure the library manager has been loaded before we
// are constructed so that it is destroyed after us and thus does
// not close any loaded DLLs with loaded algorithms in them
Mantid::Kernel::LibraryManager::Instance();
}
IFunction_sptr
FunctionFactoryImpl::createFunction(const std::string &type) const {
IFunction_sptr fun = create(type);
fun->initialize();
return fun;
}
Roman Tolchenov
committed
/**Creates an instance of a function
* @param input :: An input string which defines the function and initial values
* for the parameters.
* Parameters of different functions are separated by ';'. Parameters of the
* same function
* are separated by ','. parameterName=value pairs are used to set a parameter
* value. For each function
* "name" parameter must be set to a function name. E.g.
* input = "name=LinearBackground,A0=0,A1=1; name = Gaussian,
* PeakCentre=10.,Sigma=1"
* @return A pointer to the created function
*/
IFunction_sptr
FunctionFactoryImpl::createInitialized(const std::string &input) const {
Expression expr;
try {
expr.parse(input);
} catch (Expression::ParsingError &e) {
inputError(input + "\n " + e.what());
} catch (...) {
inputError(input);
}
Roman Tolchenov
committed
const Expression &e = expr.bracketsRemoved();
std::map<std::string, std::string> parentAttributes;
if (e.name() == ";") {
IFunction_sptr fun = createComposite(e, parentAttributes);
if (!fun)
inputError();
return fun;
}
Roman Tolchenov
committed
return createSimple(e, parentAttributes);
}
Roman Tolchenov
committed
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
/**
* @param input :: An input string which defines the function and initial values
* for the parameters.
* Parameters of different functions are separated by ';'. Parameters of the
* same function
* are separated by ','. parameterName=value pairs are used to set a parameter
* value. For each function
* "name" parameter must be set to a function name. E.g.
* input = "name=LinearBackground,A0=0,A1=1; name = Gaussian,
* PeakCentre=10.,Sigma=1"
* @param domainNumber :: The number of domains to add to the function.
* @return A pointer to the created function.
*/
boost::shared_ptr<MultiDomainFunction>
FunctionFactoryImpl::createInitializedMultiDomainFunction(
const std::string &input, size_t domainNumber) {
auto singleFunction = createInitialized(input);
auto multiDomainFunction = boost::make_shared<MultiDomainFunction>();
if (!singleFunction) {
return multiDomainFunction;
}
for (size_t i = 0; i < domainNumber; ++i) {
multiDomainFunction->addFunction(singleFunction->clone());
multiDomainFunction->setDomainIndex(i, i);
}
return multiDomainFunction;
}
/**
* Create a function from an expression.
* @param expr :: The input expression
* @param parentAttributes :: An output map filled with the attribute name &
* values of the parent function
* @return A pointer to the created function
*/
IFunction_sptr FunctionFactoryImpl::createSimple(
const Expression &expr,
std::map<std::string, std::string> &parentAttributes) const {
if (expr.name() == "=" && expr.size() > 1) {
return createFunction(expr.terms()[1].name());
}
Roman Tolchenov
committed
if (expr.name() != "," || expr.size() == 0) {
inputError(expr.str());
}
Roman Tolchenov
committed
const std::vector<Expression> &terms = expr.terms();
auto term = terms.cbegin();
Roman Tolchenov
committed
if (term->name() != "=")
inputError(expr.str());
if (term->terms()[0].name() != "name" &&
term->terms()[0].name() != "composite") {
throw std::invalid_argument(
"Function name must be defined before its parameters");
}
std::string fnName = term->terms()[1].name();
Peterson, Peter
committed
IFunction_sptr fun = createFunction(fnName);
for (++term; term != terms.end();
++term) { // loop over function's parameters/attributes
if (term->name() != "=")
inputError(expr.str());
std::string parName = term->terms()[0].name();
std::string parValue = term->terms()[1].str();
if (fun->hasAttribute(parName)) {
// set attribute
if (parValue.size() > 1 && parValue[0] == '"') {
// remove the double quotes
parValue = parValue.substr(1, parValue.size() - 2);
Peterson, Peter
committed
}
IFunction::Attribute att = fun->getAttribute(parName);
att.fromString(parValue);
fun->setAttribute(parName, att);
} else if (parName.size() >= 10 && parName.substr(0, 10) == "constraint") {
// or it can be a list of constraints
addConstraints(fun, (*term)[1].bracketsRemoved());
addTies(fun, (*term)[1].bracketsRemoved());
} else if (!parName.empty() && parName[0] == '$') {
parName.erase(0, 1);
parentAttributes[parName] = parValue;
} else {
// set initial parameter value
try {
fun->setParameter(parName, boost::lexical_cast<double>(parValue));
} catch (boost::bad_lexical_cast &) {
throw std::runtime_error(
Hahn, Steven
committed
std::string("Error in value of parameter ")
.append(parName)
.append(".\n")
.append(parValue)
.append(" cannot be interpreted as a floating point value."));
Peterson, Peter
committed
}
Peterson, Peter
committed
fun->applyTies();
return fun;
}
Peterson, Peter
committed
/**
* Create a composite function from an expression.
* @param expr :: The input expression
* @param parentAttributes :: An output map filled with the attribute name &
* values of the parent function
* @return A pointer to the created function
*/
CompositeFunction_sptr FunctionFactoryImpl::createComposite(
const Expression &expr,
std::map<std::string, std::string> &parentAttributes) const {
if (expr.name() != ";")
inputError(expr.str());
Peterson, Peter
committed
if (expr.size() == 0) {
return CompositeFunction_sptr();
}
Peterson, Peter
committed
const std::vector<Expression> &terms = expr.terms();
auto it = terms.cbegin();
const Expression &term = it->bracketsRemoved();
CompositeFunction_sptr cfun;
if (term.name() == "=") {
if (term.terms()[0].name() == "composite") {
cfun = std::dynamic_pointer_cast<CompositeFunction>(
createFunction(term.terms()[1].name()));
if (!cfun)
inputError(expr.str());
++it;
} else if (term.terms()[0].name() == "name") {
cfun = std::dynamic_pointer_cast<CompositeFunction>(
createFunction("CompositeFunction"));
if (!cfun)
inputError(expr.str());
} else {
inputError(expr.str());
}
} else if (term.name() == ",") {
auto firstTerm = term.terms().cbegin();
if (firstTerm->name() == "=") {
if (firstTerm->terms()[0].name() == "composite") {
cfun = std::dynamic_pointer_cast<CompositeFunction>(
createSimple(term, parentAttributes));
if (!cfun)
Peterson, Peter
committed
inputError(expr.str());
++it;
} else if (firstTerm->terms()[0].name() == "name") {
cfun = std::dynamic_pointer_cast<CompositeFunction>(
createFunction("CompositeFunction"));
if (!cfun)
inputError(expr.str());
} else {
Peterson, Peter
committed
inputError(expr.str());
}
}
cfun = std::dynamic_pointer_cast<CompositeFunction>(
createFunction("CompositeFunction"));
if (!cfun)
inputError(expr.str());
} else {
inputError(expr.str());
}
Peterson, Peter
committed
if (!cfun)
inputError(expr.str());
for (; it != terms.end(); ++it) {
const Expression ¤tTerm = it->bracketsRemoved();
IFunction_sptr fun;
std::map<std::string, std::string> pAttributes;
if (currentTerm.name() == ";") {
fun = createComposite(currentTerm, pAttributes);
if (!fun)
continue;
} else {
std::string parName = currentTerm[0].name();
if (parName.size() >= 10 && parName.substr(0, 10) == "constraint") {
addConstraints(cfun, currentTerm[1].bracketsRemoved());
continue;
} else if (parName == "ties") {
addTies(cfun, currentTerm[1].bracketsRemoved());
fun = createSimple(currentTerm, pAttributes);
Peterson, Peter
committed
}
}
cfun->addFunction(fun);
size_t i = cfun->nFunctions() - 1;
for (auto &pAttribute : pAttributes) {
// Apply parent attributes of the child function to this function. If this
// function doesn't have those attributes, they get passed up the chain to
// this function's parent.
if (cfun->hasLocalAttribute(pAttribute.first)) {
cfun->setLocalAttributeValue(i, pAttribute.first, pAttribute.second);
} else {
parentAttributes[pAttribute.first] = pAttribute.second;
}
Peterson, Peter
committed
}
Peterson, Peter
committed
if (cfun) {
cfun->applyTies();
}
Peterson, Peter
committed
/// Throw an exception
void FunctionFactoryImpl::inputError(const std::string &str) const {
std::string msg("Error in input string to FunctionFactory");
if (!str.empty()) {
msg += "\n" + str;
}
throw std::invalid_argument(msg);
}
Peterson, Peter
committed
/**
* Add constraints to the created function
* @param fun :: The function
* @param expr :: The constraint expression. The expression name must be either
* a single constraint
* expression such as "0 < Sigma < 1" or a list of constraint expressions
* separated by commas ','
* and enclosed in brackets "(...)" .
*/
void FunctionFactoryImpl::addConstraints(const IFunction_sptr &fun,
const Expression &expr) const {
if (expr.name() == ",") {
for (auto it = expr.begin(); it != expr.end(); ++it) {
// If this is a penalty term, we used it on the previous iteration
// so we move on to the next term.
auto constraint = (*it);
std::string constraint_term = constraint.terms()[0].str();
if (constraint_term.compare("penalty") == 0) {
if ((it + 1) != expr.end()) {
auto next_constraint = *(it + 1);
std::string next_term = next_constraint.terms()[0].str();
if (next_term.compare("penalty") == 0) {
addConstraint(fun, constraint, next_constraint);
addConstraint(fun, constraint);
}
} else {
addConstraint(fun, constraint);
Peterson, Peter
committed
}
} else { // There was a single constraint given, cannot contain a penalty
addConstraint(fun, expr);
}
}
/**
* Add a constraints to the function
* @param fun :: The function
* @param expr :: The constraint expression.
*/
void FunctionFactoryImpl::addConstraint(const std::shared_ptr<IFunction> &fun,
ConstraintFactory::Instance().createInitialized(fun.get(), expr));
c->setPenaltyFactor(c->getDefaultPenaltyFactor());
fun->addConstraint(std::move(c));
Peterson, Peter
committed
/**
* Add a constraint to the function with non-default penalty
* @param fun :: The function
* @param constraint_expr :: The constraint expression.
* @param penalty_expr :: The penalty expression.
*/
void FunctionFactoryImpl::addConstraint(const std::shared_ptr<IFunction> &fun,
const Expression &constraint_expr,
const Expression &penalty_expr) const {
auto c = std::unique_ptr<IConstraint>(
ConstraintFactory::Instance().createInitialized(fun.get(),
constraint_expr));
double penalty_factor = std::stof(penalty_expr.terms()[1].str(), NULL);
c->setPenaltyFactor(penalty_factor);
fun->addConstraint(std::move(c));
Peterson, Peter
committed
/**
* @param fun :: The function
* @param expr :: The tie expression: either parName = TieString or a list
* of name = string pairs
*/
void FunctionFactoryImpl::addTies(const IFunction_sptr &fun,
const Expression &expr) const {
if (expr.name() == "=") {
addTie(fun, expr);
} else if (expr.name() == ",") {
for (const auto &constraint : expr) {
addTie(fun, constraint);
}
/**
* @param fun :: The function
* @param expr :: The tie expression: parName = TieString
*/
void FunctionFactoryImpl::addTie(const IFunction_sptr &fun,
const Expression &expr) const {
if (expr.size() > 1) { // if size > 2 it is interpreted as setting a tie (last
// f1.alpha = f2.alpha = f3.alpha = f0.beta^2/2
const std::string value = expr[expr.size() - 1].str();
for (size_t i = expr.size() - 1; i != 0;) {
--i;
fun->tie(expr[i].name(), value);
}
std::vector<std::string> FunctionFactoryImpl::getFunctionNamesGUI() const {
auto allNames = getFunctionNames<IFunction1D>();
allNames.emplace_back("ProductFunction");
allNames.emplace_back("CompositeFunction");
allNames.emplace_back("Convolution");
std::sort(allNames.begin(), allNames.end());
std::vector<std::string> names;
names.reserve(allNames.size());
auto excludes =
Kernel::ConfigService::Instance().getString("curvefitting.guiExclude");
Kernel::StringTokenizer tokenizer(excludes, ";",
Kernel::StringTokenizer::TOK_TRIM);
std::set<std::string> excludeList(tokenizer.begin(), tokenizer.end());
std::copy_if(allNames.cbegin(), allNames.cend(), std::back_inserter(names),
[&excludeList](const auto &name) {
return excludeList.count(name) == 0;
});
void FunctionFactoryImpl::subscribe(
const std::string &className,
std::unique_ptr<AbstractFactory> pAbstractFactory,
Kernel::DynamicFactory<IFunction>::SubscribeAction replace) {
// Clear the cache, then do all the work in the base class method
m_cachedFunctionNames.clear();
Kernel::DynamicFactory<IFunction>::subscribe(
className, std::move(pAbstractFactory), replace);
}
void FunctionFactoryImpl::unsubscribe(const std::string &className) {
// Clear the cache, then do all the work in the base class method
m_cachedFunctionNames.clear();
Kernel::DynamicFactory<IFunction>::unsubscribe(className);
}
} // namespace Mantid