Skip to content
Snippets Groups Projects
Algorithm.cpp 72.5 KiB
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
// SPDX - License - Identifier: GPL - 3.0 +
#include "MantidAPI/ADSValidator.h"
#include "MantidAPI/AlgorithmHistory.h"
#include "MantidAPI/AlgorithmManager.h"
#include "MantidAPI/AlgorithmProxy.h"
#include "MantidAPI/AnalysisDataService.h"
#include "MantidAPI/DeprecatedAlgorithm.h"
#include "MantidAPI/IWorkspaceProperty.h"
#include "MantidAPI/WorkspaceGroup.h"
#include "MantidAPI/WorkspaceHistory.h"
#include "MantidKernel/CompositeValidator.h"
Nick Draper's avatar
Nick Draper committed
#include "MantidKernel/ConfigService.h"
#include "MantidKernel/DateAndTime.h"
#include "MantidKernel/EmptyValues.h"
#include "MantidKernel/MultiThreaded.h"
#include "MantidKernel/PropertyWithValue.h"
#include "MantidKernel/Strings.h"
#include "MantidKernel/Timer.h"
Nick Draper's avatar
Nick Draper committed
#include "MantidKernel/UsageService.h"
#include "MantidParallel/Communicator.h"

#include <boost/weak_ptr.hpp>
#include "MantidKernel/StringTokenizer.h"
#include <Poco/ActiveMethod.h>
#include <Poco/ActiveResult.h>
#include <Poco/NotificationCenter.h>
#include <Poco/RWLock.h>
#include <Poco/Void.h>
Nick Draper's avatar
Nick Draper committed

#include <json/json.h>
// Index property handling template definitions
#include "MantidAPI/Algorithm.tcc"

namespace Mantid {
namespace API {
namespace {
/// Separator for workspace types in workspaceMethodOnTypes member
const std::string WORKSPACE_TYPES_SEPARATOR = ";";

class WorkspacePropertyValueIs {
public:
  explicit WorkspacePropertyValueIs(const std::string &value)
  bool operator()(IWorkspaceProperty *property) {
    auto *prop = dynamic_cast<Property *>(property);
    if (!prop)
      return false;
    return prop->value() == m_value;
  }

private:
  const std::string &m_value;

// Doxygen can't handle member specialization at the moment:
// https://bugzilla.gnome.org/show_bug.cgi?id=406027
// so we have to ignore them
///@cond
template <typename NumT> bool Algorithm::isEmpty(const NumT toCheck) {
  return static_cast<int>(toCheck) == EMPTY_INT();
}

template <> MANTID_API_DLL bool Algorithm::isEmpty(const double toCheck) {
  return std::abs((toCheck - EMPTY_DBL()) / (EMPTY_DBL())) < 1e-8;
}

// concrete instantiations
template MANTID_API_DLL bool Algorithm::isEmpty<int>(const int);
template MANTID_API_DLL bool Algorithm::isEmpty<int64_t>(const int64_t);
template MANTID_API_DLL bool Algorithm::isEmpty<std::size_t>(const std::size_t);
///@endcond

//=============================================================================================
//================================== Constructors/Destructors
//=================================
//=============================================================================================

/// Initialize static algorithm counter
size_t Algorithm::g_execCount = 0;

/// Constructor
Algorithm::Algorithm()
    : PropertyManagerOwner(), m_cancel(false), m_parallelException(false),
      m_log("Algorithm"), g_log(m_log), m_groupSize(0), m_executeAsync(nullptr),
      m_notificationCenter(nullptr), m_progressObserver(nullptr),
      m_executionState(ExecutionState::Uninitialized),
      m_resultState(ResultState::NotFinished), m_isExecuted(false),
      m_isChildAlgorithm(false), m_recordHistoryForChild(false),
      m_alwaysStoreInADS(true), m_runningAsync(false), m_rethrow(false),
      m_isAlgStartupLoggingEnabled(true), m_startChildProgress(0.),
      m_endChildProgress(0.), m_algorithmID(this), m_singleGroup(-1),
      m_groupsHaveSimilarNames(false), m_inputWorkspaceHistories(),
      m_communicator(std::make_unique<Parallel::Communicator>()) {}

/// Virtual destructor

//=============================================================================================
//================================== Simple Getters/Setters
//===================================
//=============================================================================================

/// Gets the current execution state
ExecutionState Algorithm::executionState() const { return m_executionState; }

/// Sets the current execution state
void Algorithm::setExecutionState(const ExecutionState state) {
  m_executionState = state;
}

/// Gets the current result State
ResultState Algorithm::resultState() const { return m_resultState; }

/// Sets the result execution state
/// if set to Success or Failed will also set the execution state to finished
void Algorithm::setResultState(const ResultState state) {
  if (state != ResultState::NotFinished) {
    setExecutionState(ExecutionState::Finished);
  }
//---------------------------------------------------------------------------------------------
/// Has the Algorithm already been initialized
bool Algorithm::isInitialized() const {
  return (m_executionState != ExecutionState::Uninitialized);

/// Has the Algorithm already been executed
bool Algorithm::isExecuted() const {
  return ((executionState() == ExecutionState::Finished) &&
          (resultState() == ResultState::Success));

//---------------------------------------------------------------------------------------------
/** To query whether algorithm is a child.
 *  @returns true - the algorithm is a child algorithm.  False - this is a full
 * managed algorithm.
 */
bool Algorithm::isChild() const { return m_isChildAlgorithm; }

/** To set whether algorithm is a child.
 *  @param isChild :: True - the algorithm is a child algorithm.  False - this
 * is a full managed algorithm.
 */
void Algorithm::setChild(const bool isChild) {
  m_isChildAlgorithm = isChild;
  this->setAlwaysStoreInADS(!isChild);

/**
 * Change the state of the history recording flag. Only applicable for
 * child algorithms.
 * @param on :: The new state of the flag
 */
void Algorithm::enableHistoryRecordingForChild(const bool on) {
  m_recordHistoryForChild = on;
}

/** Do we ALWAYS store in the AnalysisDataService? This is set to true
 * for python algorithms' child algorithms
 *
 * @param doStore :: always store in ADS
 */
void Algorithm::setAlwaysStoreInADS(const bool doStore) {
  m_alwaysStoreInADS = doStore;
}

/** Returns true if we always store in the AnalysisDataService.
 *  @return true if output is saved to the AnalysisDataService.
 */
bool Algorithm::getAlwaysStoreInADS() const { return m_alwaysStoreInADS; }

/** Set whether the algorithm will rethrow exceptions
 * @param rethrow :: true if you want to rethrow exception.
 */
void Algorithm::setRethrows(const bool rethrow) { this->m_rethrow = rethrow; }

/// True if the algorithm is running.
bool Algorithm::isRunning() const {
  return (executionState() == ExecutionState::Running);
}

//---------------------------------------------------------------------------------------------
/**  Add an observer to a notification
@param observer :: Reference to the observer to add
*/
void Algorithm::addObserver(const Poco::AbstractObserver &observer) const {
  notificationCenter().addObserver(observer);
}

/**  Remove an observer
@param observer :: Reference to the observer to remove
*/
void Algorithm::removeObserver(const Poco::AbstractObserver &observer) const {
  notificationCenter().removeObserver(observer);
}

//---------------------------------------------------------------------------------------------
/** Sends ProgressNotification.
 * @param p :: Reported progress,  must be between 0 (just started) and 1
 * (finished)
 * @param msg :: Optional message string
 * @param estimatedTime :: Optional estimated time to completion
 * @param progressPrecision :: optional, int number of digits after the decimal
 * point to show.
void Algorithm::progress(double p, const std::string &msg, double estimatedTime,
                         int progressPrecision) {
  notificationCenter().postNotification(
      new ProgressNotification(this, p, msg, estimatedTime, progressPrecision));
}

//---------------------------------------------------------------------------------------------
/// Function to return all of the categories that contain this algorithm
const std::vector<std::string> Algorithm::categories() const {
  Mantid::Kernel::StringTokenizer tokenizer(
      category(), categorySeparator(),
      Mantid::Kernel::StringTokenizer::TOK_TRIM |
          Mantid::Kernel::StringTokenizer::TOK_IGNORE_EMPTY);
  auto res = tokenizer.asVector();
  const auto *depo = dynamic_cast<const DeprecatedAlgorithm *>(this);
    res.emplace_back("Deprecated");
  }
  return res;
}

/**
 * @return A string giving the method name that should be attached to a
 * workspace
 */
const std::string Algorithm::workspaceMethodName() const { return ""; }

/**
 *
 * @return A list of workspace class names that should have the
 *workspaceMethodName attached
 */
const std::vector<std::string> Algorithm::workspaceMethodOn() const {
  Mantid::Kernel::StringTokenizer tokenizer(
      this->workspaceMethodOnTypes(), WORKSPACE_TYPES_SEPARATOR,
      Mantid::Kernel::StringTokenizer::TOK_TRIM |
          Mantid::Kernel::StringTokenizer::TOK_IGNORE_EMPTY);
  return tokenizer.asVector();
}

/**
 * @return The name of the property that the calling object will be passed to.
 */
const std::string Algorithm::workspaceMethodInputProperty() const { return ""; }

//---------------------------------------------------------------------------------------------
/** Initialization method invoked by the framework. This method is responsible
 *  for any bookkeeping of initialization required by the framework itself.
 *  It will in turn invoke the init() method of the derived algorithm,
 *  and of any Child Algorithms which it creates.
 *  @throw runtime_error Thrown if algorithm or Child Algorithm cannot be
 *initialised
 *
 */
void Algorithm::initialize() {
  // Bypass the initialization if the algorithm has already been initialized.
    return;

  g_log.setName(this->name());
  try {
    try {
      this->init();
    } catch (std::runtime_error &) {
      throw;
    // Indicate that this Algorithm has been initialized to prevent duplicate
    // attempts.
    setExecutionState(ExecutionState::Initialized);
  } catch (std::runtime_error &) {
    throw;
  }
  // Unpleasant catch-all! Along with this, Gaudi version catches GaudiException
  // & std::exception
  // but doesn't really do anything except (print fatal) messages.
  catch (...) {
    // Gaudi: A call to the auditor service is here
    // (1) perform the printout
    getLogger().fatal("UNKNOWN Exception is caught in initialize()");
    throw;
  }
}

//---------------------------------------------------------------------------------------------
/** Perform validation of ALL the input properties of the algorithm.
 * This is to be overridden by specific algorithms.
 * It will be called in dialogs after parsing all inputs and setting the
 * properties, but BEFORE executing.
 *
 * @return a map where: Key = string name of the the property;
            Value = string describing the problem with the property.
 */
std::map<std::string, std::string> Algorithm::validateInputs() {
  return std::map<std::string, std::string>();
}

//---------------------------------------------------------------------------------------------
/**
 * Go through the properties and cache the input/output
 * workspace properties for later use.
 */
void Algorithm::cacheWorkspaceProperties() {
  m_inputWorkspaceProps.clear();
  m_outputWorkspaceProps.clear();
  m_pureOutputWorkspaceProps.clear();
  const auto &props = this->getProperties();
  for (const auto &prop : props) {
    auto wsProp = dynamic_cast<IWorkspaceProperty *>(prop);
    if (!wsProp)
      continue;
    switch (prop->direction()) {
    case Kernel::Direction::Input:
      m_inputWorkspaceProps.emplace_back(wsProp);
      break;
    case Kernel::Direction::InOut:
      m_inputWorkspaceProps.emplace_back(wsProp);
      m_outputWorkspaceProps.emplace_back(wsProp);
      break;
    case Kernel::Direction::Output:
      m_outputWorkspaceProps.emplace_back(wsProp);
      m_pureOutputWorkspaceProps.emplace_back(wsProp);
      break;
    default:
      throw std::logic_error(
          "Unexpected property direction found for property " + prop->name() +
          " of algorithm " + this->name());
    }
  }
/**
 * Cache the histories of any input workspaces so they can be copied over after
 * algorithm completion.
 */
void Algorithm::cacheInputWorkspaceHistories() {
  if (!trackingHistory())
    return;

  auto cacheHistories = [this](const Workspace_sptr &ws) {
    if (auto group = dynamic_cast<const WorkspaceGroup *>(ws.get())) {
      m_inputWorkspaceHistories.reserve(m_inputWorkspaceHistories.size() +
                                        group->size());
      for (const auto &memberWS : *group) {
        m_inputWorkspaceHistories.emplace_back(memberWS);
      }
    } else {
      m_inputWorkspaceHistories.emplace_back(ws);
    }
  };
  using ArrayPropertyString = ArrayProperty<std::string>;
  auto isADSValidator = [](const IValidator_sptr &validator) -> bool {
    if (!validator)
      return false;
    if (dynamic_cast<ADSValidator *>(validator.get()))
      return true;
    if (const auto compValidator =
            dynamic_cast<CompositeValidator *>(validator.get()))
      return compValidator->contains<ADSValidator>();

    return false;
  };

  // Look over all properties so we can catch an string array properties
  // with an ADSValidator. ADSValidator indicates that the strings
  // point to workspace names so we want to pick up the history from these too.
  const auto &ads = AnalysisDataService::Instance();
  m_inputWorkspaceHistories.clear();
  const auto &props = this->getProperties();
  for (const auto &prop : props) {
    if (prop->direction() != Direction::Input &&
        prop->direction() != Direction::InOut)
      continue;

    if (auto wsProp = dynamic_cast<IWorkspaceProperty *>(prop)) {
      if (auto ws = wsProp->getWorkspace()) {
        cacheHistories(ws);
      } else {
        Workspace_sptr wsFromADS;
        try {
          wsFromADS = ads.retrieve(prop->value());
        } catch (Exception::NotFoundError &) {
          continue;
        }
        cacheHistories(wsFromADS);
      }
    } else if (auto strArrayProp = dynamic_cast<ArrayPropertyString *>(prop)) {
      if (!isADSValidator(strArrayProp->getValidator()))
        continue;
      const auto &wsNames((*strArrayProp)());
      for (const auto &name : wsNames) {
        cacheHistories(ads.retrieve(name));
      }
    }
  }
} // namespace API

//---------------------------------------------------------------------------------------------
/** Go through the workspace properties of this algorithm
 * and lock the workspaces for reading or writing.
 *
 */
void Algorithm::lockWorkspaces() {
  // Do not lock workspace for child algos
  if (this->isChild())
    return;

  if (!m_readLockedWorkspaces.empty() || !m_writeLockedWorkspaces.empty())
    throw std::logic_error("Algorithm::lockWorkspaces(): The workspaces have "
                           "already been locked!");

  // First, Write-lock the output workspaces
  auto &debugLog = g_log.debug();
  for (auto &outputWorkspaceProp : m_outputWorkspaceProps) {
    Workspace_sptr ws = outputWorkspaceProp->getWorkspace();
    if (ws) {
      // The workspace property says to do locking,
      // AND it has NOT already been write-locked
      if (outputWorkspaceProp->isLocking() &&
          std::find(m_writeLockedWorkspaces.begin(),
                    m_writeLockedWorkspaces.end(),
                    ws) == m_writeLockedWorkspaces.end()) {
        // Write-lock it if not already
        debugLog << "Write-locking " << ws->getName() << '\n';
        ws->getLock()->writeLock();
        m_writeLockedWorkspaces.emplace_back(ws);
  // Next read-lock the input workspaces
  for (auto &inputWorkspaceProp : m_inputWorkspaceProps) {
    Workspace_sptr ws = inputWorkspaceProp->getWorkspace();
    if (ws) {
      // The workspace property says to do locking,
      // AND it has NOT already been write-locked
      if (inputWorkspaceProp->isLocking() &&
          std::find(m_writeLockedWorkspaces.begin(),
                    m_writeLockedWorkspaces.end(),
                    ws) == m_writeLockedWorkspaces.end()) {
        // Read-lock it if not already write-locked
        debugLog << "Read-locking " << ws->getName() << '\n';
        ws->getLock()->readLock();
        m_readLockedWorkspaces.emplace_back(ws);
  }
}

//---------------------------------------------------------------------------------------------
/** Unlock any previously locked workspaces
 *
 */
void Algorithm::unlockWorkspaces() {
  // Do not lock workspace for child algos
  if (this->isChild())
    return;
  auto &debugLog = g_log.debug();
  for (auto &ws : m_writeLockedWorkspaces) {
      debugLog << "Unlocking " << ws->getName() << '\n';
      ws->getLock()->unlock();
  for (auto &ws : m_readLockedWorkspaces) {
      debugLog << "Unlocking " << ws->getName() << '\n';
      ws->getLock()->unlock();
  // Don't double-unlock workspaces
  m_readLockedWorkspaces.clear();
  m_writeLockedWorkspaces.clear();
}

//---------------------------------------------------------------------------------------------
/** Invoced internally in execute()

bool Algorithm::executeInternal() {
  bool algIsExecuted = false;
  AlgorithmManager::Instance().notifyAlgorithmStarting(this->getAlgorithmID());
  {
    auto *depo = dynamic_cast<DeprecatedAlgorithm *>(this);
      getLogger().error(depo->deprecationMsg(this));
  }
  notificationCenter().postNotification(new StartedNotification(this));
  Mantid::Types::Core::DateAndTime startTime;
Nick Draper's avatar
Nick Draper committed

  // Return a failure if the algorithm hasn't been initialized
  if (!isInitialized()) {
    throw std::runtime_error("Algorithm is not initialised:" + this->name());
  }

  // no logging of input if a child algorithm (except for python child algos)
  if (!m_isChildAlgorithm || m_alwaysStoreInADS)
    logAlgorithmInfo();

  // Check all properties for validity
  constexpr bool resetTimer{true};
  float timingInit = timer.elapsed(resetTimer);
  if (!validateProperties()) {
    // Reset name on input workspaces to trigger attempt at collection from ADS
    const auto &props = getProperties();
    for (auto &prop : props) {
      auto wsProp = dynamic_cast<IWorkspaceProperty *>(prop);
      if (wsProp && !(wsProp->getWorkspace())) {
        // Setting it's name to the same one it already had
        prop->setValue(prop->value());
    // Try the validation again
    if (!validateProperties()) {
      notificationCenter().postNotification(
          new ErrorNotification(this, "Some invalid Properties found"));
      throw std::runtime_error("Some invalid Properties found");
  const float timingPropertyValidation = timer.elapsed(resetTimer);
  // All properties are now valid - cache workspace properties and histories
  cacheWorkspaceProperties();
  cacheInputWorkspaceHistories();

  // ----- Check for processing groups -------------
  // default true so that it has the right value at the check below the catch
  // block should checkGroups throw
  bool callProcessGroups = true;
  try {
    // Checking the input is a group. Throws if the sizes are wrong
    callProcessGroups = this->checkGroups();
  } catch (std::exception &ex) {
    getLogger().error() << "Error in execution of algorithm " << this->name()
                        << "\n"
                        << ex.what() << "\n";
    notificationCenter().postNotification(
        new ErrorNotification(this, ex.what()));
    setResultState(ResultState::Failed);
    if (m_isChildAlgorithm || m_runningAsync || m_rethrow) {
      m_runningAsync = false;
      throw;
  const auto executionMode = getExecutionMode();

  timingInit += timer.elapsed(resetTimer);
  // ----- Perform validation of the whole set of properties -------------
  if ((!callProcessGroups) &&
      (executionMode != Parallel::ExecutionMode::MasterOnly ||
       communicator().rank() == 0)) // for groups this is called on each
                                    // workspace separately
  {
    std::map<std::string, std::string> errors = this->validateInputs();
    if (!errors.empty()) {
      size_t numErrors = errors.size();
      // Log each issue
      auto &errorLog = getLogger().error();
      auto &warnLog = getLogger().warning();
      for (auto &error : errors) {
        if (this->existsProperty(error.first))
          errorLog << "Invalid value for " << error.first << ": "
                   << error.second << "\n";
        else {
          numErrors -= 1; // don't count it as an error
          warnLog << "validateInputs() references non-existant property \""
                  << error.first << "\"\n";
      // Throw because something was invalid
      if (numErrors > 0) {
        notificationCenter().postNotification(
            new ErrorNotification(this, "Some invalid Properties found"));
        throw std::runtime_error("Some invalid Properties found");
  const float timingInputValidation = timer.elapsed(resetTimer);
  if (trackingHistory()) {
    // count used for defining the algorithm execution order
    // If history is being recorded we need to count this as a separate
    // algorithm
    // as the history compares histories by their execution number
    ++Algorithm::g_execCount;

    // populate history record before execution so we can record child
    // algorithms in it
    AlgorithmHistory algHist;
    m_history = boost::make_shared<AlgorithmHistory>(algHist);
  }
  // ----- Process groups -------------
  // If checkGroups() threw an exception but there ARE group workspaces
  // (means that the group sizes were incompatible)
  if (callProcessGroups) {
  // Read or write locks every input/output workspace
  this->lockWorkspaces();
  timingInit += timer.elapsed(resetTimer);
  // Invoke exec() method of derived class and catch all uncaught exceptions
  try {
    try {
      setExecutionState(ExecutionState::Running);
      startTime = Mantid::Types::Core::DateAndTime::getCurrentTime();
      // Call the concrete algorithm's exec method
      registerFeatureUsage();
      // Check for a cancellation request in case the concrete algorithm doesn't
      interruption_point();
      const float timingExec = timer.elapsed(resetTimer);
      // The total runtime including all init steps is used for general logging.
      const float duration = timingInit + timingPropertyValidation +
                             timingInputValidation + timingExec;
      // need it to throw before trying to run fillhistory() on an algorithm
      // which has failed
      if (trackingHistory() && m_history) {
        m_history->fillAlgorithmHistory(this, startTime, duration,
                                        Algorithm::g_execCount);
        fillHistory();
        linkHistoryWithLastChild();
      // Put the output workspaces into the AnalysisDataService - if requested
      if (m_alwaysStoreInADS)
        this->store();
      // just cache the value internally, it is set at the very end of this
      // method
      algIsExecuted = true;
      // Log that execution has completed.
      getLogger().debug(
          "Time to validate properties: " +
          std::to_string(timingPropertyValidation) + " seconds\n" +
          "Time for other input validation: " +
          std::to_string(timingInputValidation) + " seconds\n" +
          "Time for other initialization: " + std::to_string(timingInit) +
          " seconds\n" + "Time to run exec: " + std::to_string(timingExec) +
          " seconds\n");
      reportCompleted(duration);
    } catch (std::runtime_error &ex) {
      setResultState(ResultState::Failed);
      this->unlockWorkspaces();
      if (m_isChildAlgorithm || m_runningAsync || m_rethrow)
Nick Draper's avatar
Nick Draper committed
        throw;
        getLogger().error()
            << "Error in execution of algorithm " << this->name() << '\n'
            << ex.what() << '\n';
      notificationCenter().postNotification(
          new ErrorNotification(this, ex.what()));
    } catch (std::logic_error &ex) {
      setResultState(ResultState::Failed);
      this->unlockWorkspaces();
      if (m_isChildAlgorithm || m_runningAsync || m_rethrow)
Nick Draper's avatar
Nick Draper committed
        throw;
        getLogger().error()
            << "Logic Error in execution of algorithm " << this->name() << '\n'
            << ex.what() << '\n';
      notificationCenter().postNotification(
          new ErrorNotification(this, ex.what()));
  } catch (CancelException &ex) {
    setResultState(ResultState::Failed);
    m_runningAsync = false;
    getLogger().warning() << this->name() << ": Execution cancelled by user.\n";
    notificationCenter().postNotification(
        new ErrorNotification(this, ex.what()));
    this->unlockWorkspaces();
    throw;
  }
  // Gaudi also specifically catches GaudiException & std:exception.
  catch (std::exception &ex) {
    setResultState(ResultState::Failed);
    m_runningAsync = false;

    notificationCenter().postNotification(
        new ErrorNotification(this, ex.what()));
    getLogger().error() << "Error in execution of algorithm " << this->name()
                        << ":\n"
                        << ex.what() << "\n";
    this->unlockWorkspaces();
    throw;
  }
  catch (...) {
    // Execution
    setResultState(ResultState::Failed);
    m_runningAsync = false;

    notificationCenter().postNotification(
        new ErrorNotification(this, "UNKNOWN Exception is caught in exec()"));
    getLogger().error() << this->name()
                        << ": UNKNOWN Exception is caught in exec()\n";
    this->unlockWorkspaces();
    throw;
  }
  // Unlock the locked workspaces
  this->unlockWorkspaces();

  // Only gets to here if algorithm ended normally
    setResultState(ResultState::Success);
  }
  notificationCenter().postNotification(
      new FinishedNotification(this, algIsExecuted));
  return algIsExecuted;
}

//---------------------------------------------------------------------------------------------
/** Execute as a Child Algorithm.
 * This runs execute() but catches errors so as to log the name
 * of the failed Child Algorithm, if it fails.
 */
void Algorithm::executeAsChildAlg() {
  bool executed = false;
  try {
    executed = execute();
  } catch (std::runtime_error &) {
    throw;
  }
  if (!executed) {
    throw std::runtime_error("Unable to successfully run ChildAlgorithm " +
                             this->name());
  }
}

//---------------------------------------------------------------------------------------------
/** Stores any output workspaces into the AnalysisDataService
 *  @throw std::runtime_error If unable to successfully store an output
 * workspace
 */
void Algorithm::store() {
  const std::vector<Property *> &props = getProperties();
  std::vector<int> groupWsIndicies;

  // add any regular/child workspaces first, then add the groups
  for (unsigned int i = 0; i < props.size(); ++i) {
    auto *wsProp = dynamic_cast<IWorkspaceProperty *>(props[i]);
    if (wsProp) {
      // check if the workspace is a group, if so remember where it is and add
      // it later
      auto group =
          boost::dynamic_pointer_cast<WorkspaceGroup>(wsProp->getWorkspace());
      if (!group) {
        try {
          wsProp->store();
        } catch (std::runtime_error &) {
          throw;
        groupWsIndicies.emplace_back(i);
  // now store workspace groups once their members have been added
  std::vector<int>::const_iterator wsIndex;
  for (wsIndex = groupWsIndicies.begin(); wsIndex != groupWsIndicies.end();
       ++wsIndex) {
    auto *wsProp = dynamic_cast<IWorkspaceProperty *>(props[*wsIndex]);
    if (wsProp) {
      try {
        wsProp->store();
      } catch (std::runtime_error &) {
        throw;
    }
  }
}

//---------------------------------------------------------------------------------------------
/** Create a Child Algorithm.  A call to this method creates a child algorithm
 *object.
 *  Using this mechanism instead of creating daughter
 *  algorithms directly via the new operator is prefered since then
 *  the framework can take care of all of the necessary book-keeping.
 *
 *  @param name ::           The concrete algorithm class of the Child Algorithm
 *  @param startProgress ::  The percentage progress value of the overall
 *algorithm where this child algorithm starts
 *  @param endProgress ::    The percentage progress value of the overall
 *algorithm where this child algorithm ends
 *  @param enableLogging ::  Set to false to disable logging from the child
 *algorithm
 *  @param version ::        The version of the child algorithm to create. By
 *default gives the latest version.
 *  @return shared pointer to the newly created algorithm object
 */
Algorithm_sptr Algorithm::createChildAlgorithm(const std::string &name,
                                               const double startProgress,
                                               const double endProgress,
                                               const bool enableLogging,
                                               const int &version) {
  Algorithm_sptr alg =
      AlgorithmManager::Instance().createUnmanaged(name, version);
  setupAsChildAlgorithm(alg, startProgress, endProgress, enableLogging);
  return alg;
}

/** Setup algorithm as child algorithm.
 *
 * Used internally by createChildAlgorithm. Arguments are as documented there.
 * Can also be used manually for algorithms created otherwise. This allows
 * running algorithms that are not declared into the factory as child
 * algorithms. */
void Algorithm::setupAsChildAlgorithm(Algorithm_sptr alg,
                                      const double startProgress,
                                      const double endProgress,
                                      const bool enableLogging) {
  // set as a child
  alg->setChild(true);
  alg->setLogging(enableLogging);

  // Initialise the Child Algorithm
  try {
    alg->initialize();
  } catch (std::runtime_error &) {
    throw std::runtime_error("Unable to initialise Child Algorithm '" +
                             alg->name() + "'");
  // If output workspaces are nameless, give them a temporary name to satisfy
  // validator
  const std::vector<Property *> &props = alg->getProperties();
  for (auto prop : props) {
    auto wsProp = dynamic_cast<IWorkspaceProperty *>(prop);
    if (prop->direction() == Mantid::Kernel::Direction::Output && wsProp) {
      if (prop->value().empty() && !wsProp->isOptional()) {
        prop->createTemporaryValue();
  if (startProgress >= 0.0 && endProgress > startProgress &&
      endProgress <= 1.0) {
    alg->addObserver(this->progressObserver());
    m_startChildProgress = startProgress;
    m_endChildProgress = endProgress;
  }
  // Before we return the shared pointer, use it to create a weak pointer and
  // keep that in a vector.
  // It will be used this to pass on cancellation requests
  // It must be protected by a critical block so that Child Algorithms can run
  // in parallel safely.
  boost::weak_ptr<IAlgorithm> weakPtr(alg);
  PARALLEL_CRITICAL(Algorithm_StoreWeakPtr) {
    m_ChildAlgorithms.emplace_back(weakPtr);
  }
}

//=============================================================================================
//================================== Algorithm History
//========================================
//=============================================================================================

/**
 * Serialize this object to a string. The format is
Nick Draper's avatar
Nick Draper committed
 * a json formatted string.
 * @returns This object serialized as a string
 */
std::string Algorithm::toString() const {
  ::Json::FastWriter writer;
Nick Draper's avatar
Nick Draper committed

  return writer.write(toJson());
}

/**
 * Serialize this object to a json object)
 * @returns This object serialized as a json object
 */
Nick Draper's avatar
Nick Draper committed
::Json::Value Algorithm::toJson() const {
  ::Json::Value root;

  root["name"] = name();
  root["version"] = this->version();
  root["properties"] = Kernel::PropertyManagerOwner::asJson(false);

Nick Draper's avatar
Nick Draper committed
  return root;
}

//--------------------------------------------------------------------------------------------
/** Construct an object from a history entry.
 *
 * This creates the algorithm and sets all of its properties using the history.
 *
 * @param history :: AlgorithmHistory object
 * @return a shared pointer to the created algorithm.
 */
IAlgorithm_sptr Algorithm::fromHistory(const AlgorithmHistory &history) {
  ::Json::Value root;
  ::Json::Value jsonMap;
  ::Json::FastWriter writer;

  auto props = history.getProperties();
  const size_t numProps(props.size());
  for (size_t i = 0; i < numProps; ++i) {
    PropertyHistory_sptr prop = props[i];
    if (!prop->isDefault()) {
      jsonMap[prop->name()] = prop->value();

  root["name"] = history.name();
  root["version"] = history.version();
  root["properties"] = jsonMap;

  const std::string output = writer.write(root);
  IAlgorithm_sptr alg;

  try {
    alg = Algorithm::fromString(output);
  } catch (std::invalid_argument &) {
    throw std::runtime_error(
        "Could not create algorithm from history. "
        "Is this a child algorithm whose workspaces are not in the ADS?");
  }
  return alg;
}

//--------------------------------------------------------------------------------------------
/** De-serializes the algorithm from a string
 *
 * @param input :: An input string in the format. The format is
 * AlgorithmName.version(prop1=value1,prop2=value2,...). If .version is
 * not found the highest found is used.
 * @return A pointer to a managed algorithm object
 * @throws std::runtime_error if the algorithm cannot be created
IAlgorithm_sptr Algorithm::fromString(const std::string &input) {
  ::Json::Value root;
  ::Json::Reader reader;
  if (reader.parse(input, root)) {
    return fromJson(root);
  } else {
    throw std::runtime_error("Cannot create algorithm, invalid string format.");
  }
}

/**
 * De-serializes the algorithm from a Json object
 * @param serialized A reference to Json::Value that contains a serialized
 * algorithm object
 * @return A new algorithm object
 * @throws std::runtime_error if the algorithm cannot be created
 */
IAlgorithm_sptr Algorithm::fromJson(const Json::Value &serialized) {
  const std::string algName = serialized["name"].asString();
  const int version = serialized.get("version", -1).asInt();
  auto alg = AlgorithmManager::Instance().createUnmanaged(algName, version);
  alg->initialize();
  alg->setProperties(serialized["properties"]);
  return alg;
}

//-------------------------------------------------------------------------
/** Initialize using proxy algorithm.
 * Call the main initialize method and then copy in the property values.