Skip to content
Snippets Groups Projects
MultiDomainFunction.cpp 12.1 KiB
Newer Older
//----------------------------------------------------------------------
// Includes
//----------------------------------------------------------------------
#include "MantidAPI/MultiDomainFunction.h"
#include "MantidAPI/CompositeDomain.h"
#include "MantidAPI/FunctionFactory.h"
#include "MantidAPI/Expression.h"

#include <boost/lexical_cast.hpp>
#include <set>

namespace Mantid {
namespace API {
DECLARE_FUNCTION(MultiDomainFunction)
/**
 * Associate a member function and a domain. The function will only be applied
 * to this domain.
 * @param funIndex :: Index of a member function.
 * @param domainIndex :: Index of a domain to be associated with the function.
 */
void MultiDomainFunction::setDomainIndex(size_t funIndex, size_t domainIndex) {
  m_domains[funIndex] = std::vector<size_t>(1, domainIndex);
  countNumberOfDomains();
}
/**
 * Associate a member function and a list of domains. The function will only be
 * applied
 * to the listed domains.
 * @param funIndex :: Index of a member function.
 * @param domainIndices :: A vector with indices of domains to be associated
 * with the function.
 */
void MultiDomainFunction::setDomainIndices(
    size_t funIndex, const std::vector<size_t> &domainIndices) {
  m_domains[funIndex] = domainIndices;
  countNumberOfDomains();
}
/**
 * Counts number of the domains.
 */
void MultiDomainFunction::countNumberOfDomains() {
  std::set<size_t> dSet;
  for (auto it = m_domains.begin(); it != m_domains.end(); ++it) {
    if (it->second.size()) {
      dSet.insert(it->second.begin(), it->second.end());
  m_nDomains = dSet.size();
  m_maxIndex = dSet.empty() ? 0 : *dSet.rbegin();
}
/**
 * Count value offsets for each member domain in a CompositeDomain.
 */
void MultiDomainFunction::countValueOffsets(
    const CompositeDomain &domain) const {
  m_valueOffsets.clear();
  m_valueOffsets.push_back(0);
  for (size_t i = 0; i < domain.getNParts(); ++i) {
    const FunctionDomain &d = domain.getDomain(i);
    m_valueOffsets.push_back(m_valueOffsets.back() + d.size());
/// Clear all domain indices
void MultiDomainFunction::clearDomainIndices() {
  m_domains.clear();
  countNumberOfDomains();
}
/**
 * Populates a vector with domain indices assigned to function i.
 * @param i :: Index of a function to get the domain info about.
 * @param nDomains :: Maximum number of domains.
 * @param domains :: (Output) vector to collect domain indixes.
 */
void MultiDomainFunction::getDomainIndices(size_t i, size_t nDomains,
                                           std::vector<size_t> &domains) const {
  auto it = m_domains.find(i);
  if (it == m_domains.end()) { // apply to all domains
    domains.resize(nDomains);
    for (size_t i = 0; i < domains.size(); ++i) {
      domains[i] = i;
    }
  } else { // apply to selected domains
    domains.assign(it->second.begin(), it->second.end());
/// Function you want to fit to.
/// @param domain :: The buffer for writing the calculated values. Must be big
/// enough to accept dataSize() values
void MultiDomainFunction::function(const FunctionDomain &domain,
                                   FunctionValues &values) const {
  // works only on CompositeDomain
  if (!dynamic_cast<const CompositeDomain *>(&domain)) {
    throw std::invalid_argument(
        "Non-CompositeDomain passed to MultiDomainFunction.");
  }
  const CompositeDomain &cd = dynamic_cast<const CompositeDomain &>(domain);
  // domain must not have less parts than m_maxIndex
  if (cd.getNParts() <= m_maxIndex) {
    throw std::invalid_argument(
        "CompositeDomain has too few parts (" +
        boost::lexical_cast<std::string>(cd.getNParts()) +
        ") for MultiDomainFunction (max index " +
        boost::lexical_cast<std::string>(m_maxIndex) + ").");
  }
  // domain and values must be consistent
  if (cd.size() != values.size()) {
    throw std::invalid_argument(
        "MultiDomainFunction: domain and values have different sizes.");
  }
  countValueOffsets(cd);
  // evaluate member functions
  values.zeroCalculated();
  for (size_t iFun = 0; iFun < nFunctions(); ++iFun) {
    // find the domains member function must be applied to
    std::vector<size_t> domains;
    getDomainIndices(iFun, cd.getNParts(), domains);
    for (auto i = domains.begin(); i != domains.end(); ++i) {
      const FunctionDomain &d = cd.getDomain(*i);
      FunctionValues tmp(d);
      getFunction(iFun)->function(d, tmp);
      values.addToCalculated(m_valueOffsets[*i], tmp);
/// Derivatives of function with respect to active parameters
void MultiDomainFunction::functionDeriv(const FunctionDomain &domain,
                                        Jacobian &jacobian) {
  // works only on CompositeDomain
  if (!dynamic_cast<const CompositeDomain *>(&domain)) {
    throw std::invalid_argument(
        "Non-CompositeDomain passed to MultiDomainFunction.");
  }
  if (getAttribute("NumDeriv").asBool()) {
    calNumericalDeriv(domain, jacobian);
  } else {
    const CompositeDomain &cd = dynamic_cast<const CompositeDomain &>(domain);
    // domain must not have less parts than m_maxIndex
    if (cd.getNParts() < m_maxIndex) {
      throw std::invalid_argument(
          "CompositeDomain has too few parts (" +
          boost::lexical_cast<std::string>(cd.getNParts()) +
          ") for MultiDomainFunction (max index " +
          boost::lexical_cast<std::string>(m_maxIndex) + ").");
    }
    countValueOffsets(cd);
    // evaluate member functions derivatives
    for (size_t iFun = 0; iFun < nFunctions(); ++iFun) {
      // find the domains member function must be applied to
      std::vector<size_t> domains;
      getDomainIndices(iFun, cd.getNParts(), domains);

      for (auto i = domains.begin(); i != domains.end(); ++i) {
        const FunctionDomain &d = cd.getDomain(*i);
        PartialJacobian J(&jacobian, m_valueOffsets[*i], paramOffset(iFun));
        getFunction(iFun)->functionDeriv(d, J);
      }
/**
 * Called at the start of each iteration. Call iterationStarting() of the
 * members.
 */
void MultiDomainFunction::iterationStarting() {
  for (size_t iFun = 0; iFun < nFunctions(); ++iFun) {
    getFunction(iFun)->iterationStarting();
/**
 * Called at the end of an iteration. Call iterationFinished() of the members.
 */
void MultiDomainFunction::iterationFinished() {
  for (size_t iFun = 0; iFun < nFunctions(); ++iFun) {
    getFunction(iFun)->iterationFinished();
/// Return a value of attribute attName
IFunction::Attribute
MultiDomainFunction::getLocalAttribute(size_t i,
                                       const std::string &attName) const {
  if (attName != "domains") {
    throw std::invalid_argument("MultiDomainFunction does not have attribute " +
                                attName);
  }
  if (i >= nFunctions()) {
    throw std::out_of_range("Function index is out of range.");
  }
  auto it = m_domains.find(i);
  if (it == m_domains.end()) {
    return IFunction::Attribute("All");
  } else if (it->second.size() == 1 && it->second.front() == i) {
    return IFunction::Attribute("i");
  } else if (!it->second.empty()) {
    std::string out(boost::lexical_cast<std::string>(it->second.front()));
    for (auto i = it->second.begin() + 1; i != it->second.end(); ++it) {
      out += "," + boost::lexical_cast<std::string>(*i);
    return IFunction::Attribute(out);
  return IFunction::Attribute("");
}
/**
 * Set a value to a "local" attribute, ie an attribute related to a member
 *function.
 *
 * The only attribute that can be set here is "domains" which defines the
 * indices of domains a particular function is applied to. Possible values are
 *(strings):
 *
 *     1) "All" : the function is applied to all domains defined for this
 *MultiDomainFunction.
 *     2) "i"   : the function is applied to a single domain which index is
 *equal to the
 *                function's index in this MultiDomainFunction.
 *     3) "non-negative integer" : a domain index.
 *     4) "a,b,c,..." : a list of domain indices (a,b,c,.. are non-negative
 *integers).
 *     5) "a - b" : a range of domain indices (a,b are non-negative integers a
 *<= b).
 *
 * To be used with Fit algorithm at least one of the member functions must have
 *"domains" value
 * of type 2), 3), 4) or 5) because these values can tell Fit how many domains
 *need to be created.
 *
 * @param i :: Index of a function for which the attribute is being set.
 * @param attName :: Name of an attribute.
 * @param att :: Value of the attribute to set.
 */
void MultiDomainFunction::setLocalAttribute(size_t i,
                                            const std::string &attName,
                                            const IFunction::Attribute &att) {
  if (attName != "domains") {
    throw std::invalid_argument("MultiDomainFunction does not have attribute " +
                                attName);
  }
  if (i >= nFunctions()) {
    throw std::out_of_range("Function index is out of range.");
  }
  std::string value = att.asString();
  auto it = m_domains.find(i);

  if (value == "All") { // fit to all domains
    if (it != m_domains.end()) {
      m_domains.erase(it);
    return;
  } else if (value ==
             "i") { // fit to domain with the same index as the function
    setDomainIndex(i, i);
    return;
  } else if (value.empty()) { // do not fit to any domain
    setDomainIndices(i, std::vector<size_t>());
    return;
  }
  // fit to a selection of domains
  std::vector<size_t> indx;
  Expression list;
  list.parse(value);
  if (list.name() == "+") {
    if (list.size() != 2 || list.terms()[1].operator_name() != "-") {
      throw std::runtime_error("MultiDomainFunction: attribute \"domains\" "
                               "expects two integers separated by a \"-\"");
    // value looks like "a - b". a and b must be ints and define a range of
    // domain indices
    size_t start = boost::lexical_cast<size_t>(list.terms()[0].str());
    size_t end = boost::lexical_cast<size_t>(list.terms()[1].str()) + 1;
    if (start >= end) {
      throw std::runtime_error(
          "MultiDomainFunction: attribute \"domains\": wrong range limits.");
    indx.resize(end - start);
    for (size_t i = start; i < end; ++i) {
      indx[i - start] = i;
  } else {
    // value must be either an int or a list of ints: "a,b,c,..."
    list.toList();
    for (size_t k = 0; k < list.size(); ++k) {
      indx.push_back(boost::lexical_cast<size_t>(list[k].name()));
  setDomainIndices(i, indx);
}
/**
 * Split this function into independent functions. The number of functions in
 * the
 * returned vector must be equal to the number
 * of domains. The result of evaluation of the i-th function on the i-th domain
 * must be
 * the same as if this MultiDomainFunction was evaluated.
 */
std::vector<IFunction_sptr>
MultiDomainFunction::createEquivalentFunctions() const {
  size_t nDomains = m_maxIndex + 1;
  std::vector<CompositeFunction_sptr> compositeFunctions(nDomains);
  for (size_t iFun = 0; iFun < nFunctions(); ++iFun) {
    // find the domains member function must be applied to
    std::vector<size_t> domains;
    getDomainIndices(iFun, nDomains, domains);
    for (auto i = domains.begin(); i != domains.end(); ++i) {
      size_t j = *i;
      CompositeFunction_sptr cf = compositeFunctions[j];
      if (!cf) {
        // create a composite function for each domain
        cf = CompositeFunction_sptr(new CompositeFunction());
        compositeFunctions[j] = cf;
      // add copies of all functions applied to j-th domain to a single
      // compositefunction
      cf->addFunction(FunctionFactory::Instance().createInitialized(
          getFunction(iFun)->asString()));
  }
  std::vector<IFunction_sptr> outFunctions(nDomains);
  // fill in the output vector
  // check functions containing a single member and take it out of the composite
  for (size_t i = 0; i < compositeFunctions.size(); ++i) {
    auto fun = compositeFunctions[i];
    if (!fun || fun->nFunctions() == 0) {
      throw std::runtime_error("There is no function for domain " +
                               boost::lexical_cast<std::string>(i));
    }
    if (fun->nFunctions() > 1) {
      outFunctions[i] = fun;
    } else {
      outFunctions[i] = fun->getFunction(0);
  return outFunctions;
}
} // namespace API
} // namespace Mantid