Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
PDDetermineCharacterizations.cpp 16.25 KiB
#include "MantidAlgorithms/PDDetermineCharacterizations.h"
#include "MantidAPI/ITableWorkspace.h"
#include "MantidAPI/MatrixWorkspace.h"
#include "MantidAPI/Run.h"
#include "MantidKernel/ArrayProperty.h"
#include "MantidKernel/PropertyManagerDataService.h"
#include "MantidKernel/Strings.h"
#include "MantidKernel/TimeSeriesProperty.h"

namespace Mantid {
namespace Algorithms {

using Mantid::API::ITableWorkspace_const_sptr;
using Mantid::API::WorkspaceProperty;
using Mantid::Kernel::ArrayProperty;
using Mantid::Kernel::Direction;
using Mantid::Kernel::PropertyManagerDataService;
using Mantid::Kernel::PropertyWithValue;
using Mantid::Kernel::TimeSeriesProperty;

namespace { // anonymous namespace
const std::string CHAR_PROP_NAME("Characterizations");
const std::string FREQ_PROP_NAME("FrequencyLogNames");
const std::string WL_PROP_NAME("WaveLengthLogNames");
}

// Register the algorithm into the AlgorithmFactory
DECLARE_ALGORITHM(PDDetermineCharacterizations)

/// Algorithms name for identification. @see Algorithm::name
const std::string PDDetermineCharacterizations::name() const {
  return "PDDetermineCharacterizations";
}

/// Algorithm's version for identification. @see Algorithm::version
int PDDetermineCharacterizations::version() const { return 1; }

/// Algorithm's category for identification. @see Algorithm::category
const std::string PDDetermineCharacterizations::category() const {
  return "Workflow\\Diffraction\\UsesPropertyManager";
}

/// Algorithm's summary for use in the GUI and help. @see Algorithm::summary
const std::string PDDetermineCharacterizations::summary() const {
  return "Determines the characterizations of a workspace.";
}

/**
 * These should match those in LoadPDCharacterizations
 * - "frequency" double
 * - "wavelength" (double)
 * - "bank" (integer)
 * - "container" (string)
 * - "vanadium" (string)
 * - "vanadium_background" (string)
 * - "empty" (string)
 * - "d_min" (string)
 * - "d_max" (string)
 * - "tof_min" (double)
 * - "tof_max" (double)
 * - "wavelength_min" (double)
 * - "wavelength_max" (double)
 * @return The list of expected column names
 */
std::vector<std::string> getColumnNames() {
  return {"frequency", "wavelength", "bank", "container", "vanadium",
          "vanadium_background", "empty_environment", "empty_instrument",
          "d_min", "d_max", "tof_min", "tof_max", "wavelength_min",
          "wavelength_max"};
}

/// More intesive input checking. @see Algorithm::validateInputs
std::map<std::string, std::string>
PDDetermineCharacterizations::validateInputs() {
  std::map<std::string, std::string> result;

  ITableWorkspace_const_sptr characterizations = getProperty(CHAR_PROP_NAME);

  if (!bool(characterizations))
    return result;

  std::vector<std::string> expectedNames = getColumnNames();
  std::vector<std::string> names = characterizations->getColumnNames();
  if (names.size() < expectedNames.size()) { // allow for extra columns
    std::stringstream msg;
    msg << "Encountered invalid number of columns in "
        << "TableWorkspace. Found " << names.size() << " expected "
        << expectedNames.size();
    result[CHAR_PROP_NAME] = msg.str();
  } else {
    for (auto &expectedName : expectedNames) {
      if (std::find(names.begin(), names.end(), expectedName) == names.end()) {
        std::stringstream msg;
        msg << "Failed to find column named " << expectedName;
        result[CHAR_PROP_NAME] = msg.str();
      }
    }
  }
  return result;
}

/// Initialize the algorithm's properties.
void PDDetermineCharacterizations::init() {
  declareProperty(
      Kernel::make_unique<WorkspaceProperty<>>(
          "InputWorkspace", "", Direction::Input, API::PropertyMode::Optional),
      "Workspace with logs to help identify frequency and wavelength");

  declareProperty(
      Kernel::make_unique<WorkspaceProperty<API::ITableWorkspace>>(
          CHAR_PROP_NAME, "", Direction::Input, API::PropertyMode::Optional),
      "Table of characterization information");

  declareProperty("ReductionProperties", "__pd_reduction_properties",
                  "Property manager name for the reduction");

  const std::string defaultMsg =
      " run to use. 0 to use value in table, -1 to not use.";

  declareProperty(
      Kernel::make_unique<Kernel::ArrayProperty<int32_t>>("BackRun", "0"),
      "Empty container" + defaultMsg);
  declareProperty(
      Kernel::make_unique<Kernel::ArrayProperty<int32_t>>("NormRun", "0"),
      "Normalization" + defaultMsg);
  declareProperty(
      Kernel::make_unique<Kernel::ArrayProperty<int32_t>>("NormBackRun", "0"),
      "Normalization background" + defaultMsg);
  declareProperty(
      Kernel::make_unique<Kernel::ArrayProperty<int32_t>>("EmptyEnv", "0"),
      "Empty sample environment" + defaultMsg);
  declareProperty(
      Kernel::make_unique<Kernel::ArrayProperty<int32_t>>("EmptyInstr", "0"),
      "Empty instrument" + defaultMsg);

  std::vector<std::string> defaultFrequencyNames{"SpeedRequest1", "Speed1",
                                                 "frequency"};
  declareProperty(Kernel::make_unique<Kernel::ArrayProperty<std::string>>(
                      FREQ_PROP_NAME, defaultFrequencyNames),
                  "Candidate log names for frequency");

  std::vector<std::string> defaultWavelengthNames{"LambdaRequest", "lambda"};
  declareProperty(Kernel::make_unique<Kernel::ArrayProperty<std::string>>(
                      WL_PROP_NAME, defaultWavelengthNames),
                  "Candidate log names for wave length");
}

/**
 * Compare two numbers to be in agreement within 5%
 * @param left
 * @param right
 * @return
 */
bool closeEnough(const double left, const double right) {
  // the same value
  const double diff = fabs(left - right);
  if (diff == 0.)
    return true;

  // same within 5%
  const double relativeDiff = diff * 2 / (left + right);
  return relativeDiff < .05;
}

/// Fill in the property manager from the correct line in the table
void PDDetermineCharacterizations::getInformationFromTable(
    const double frequency, const double wavelength,
    const std::string &canName) {
  size_t numRows = m_characterizations->rowCount();

  for (size_t i = 0; i < numRows; ++i) {
    const double rowFrequency =
        m_characterizations->getRef<double>("frequency", i);
    const double rowWavelength =
        m_characterizations->getRef<double>("wavelength", i);

    if (closeEnough(frequency, rowFrequency) &&
        closeEnough(wavelength, rowWavelength)) {

      // declare how the row was chosen
      g_log.information() << "Using information from row " << i
                          << " with frequency = " << rowFrequency
                          << " and wavelength = " << rowWavelength << "\n";
      m_propertyManager->setProperty("frequency", frequency);
      m_propertyManager->setProperty("wavelength", wavelength);

      // what bank number this should be called - only used at POWGEN
      m_propertyManager->setProperty(
          "bank", m_characterizations->getRef<int>("bank", i));

      // data ranges
      m_propertyManager->setPropertyValue(
          "d_min", m_characterizations->getRef<std::string>("d_min", i));
      m_propertyManager->setPropertyValue(
          "d_max", m_characterizations->getRef<std::string>("d_max", i));
      m_propertyManager->setProperty(
          "tof_min", m_characterizations->getRef<double>("tof_min", i));
      m_propertyManager->setProperty(
          "tof_max", m_characterizations->getRef<double>("tof_max", i));
      m_propertyManager->setProperty(
          "wavelength_min",
          m_characterizations->getRef<double>("wavelength_min", i));
      m_propertyManager->setProperty(
          "wavelength_max",
          m_characterizations->getRef<double>("wavelength_max", i));

      // characterization run numbers
      m_propertyManager->setProperty(
          "vanadium", m_characterizations->getRef<std::string>("vanadium", i));
      m_propertyManager->setProperty(
          "vanadium_background",
          m_characterizations->getRef<std::string>("vanadium_background", i));
      m_propertyManager->setProperty(
          "container",
          m_characterizations->getRef<std::string>("container", i));
      m_propertyManager->setProperty(
          "empty_environment",
          m_characterizations->getRef<std::string>("empty_environment", i));
      m_propertyManager->setProperty(
          "empty_instrument",
          m_characterizations->getRef<std::string>("empty_instrument", i));

      // something special if the container was specified
      if (!canName.empty()) {
        const auto columnNames = m_characterizations->getColumnNames();
        if (std::find(columnNames.begin(), columnNames.end(), canName) ==
            columnNames.end()) {
          g_log.warning() << "Failed to find container name \"" << canName
                          << "\" in characterizations table \""
                          << m_characterizations->name() << "\"\n";
        } else {
          const auto canRuns =
              m_characterizations->getRef<std::string>(canName, i);
          g_log.information() << "Updating container identifier to \""
                              << canRuns << "\"\n";
          m_propertyManager->setProperty("container", canRuns);
        }
      }

      return;
    }
  }
  g_log.warning("Failed to find compatible row in characterizations table");
}

/**
 * Get a value from one of a set of logs.
 * @param run
 * @param propName
 * @return
 */
double PDDetermineCharacterizations::getLogValue(API::Run &run,
                                                 const std::string &propName) {
  std::vector<std::string> names = getProperty(propName);

  std::string label = "frequency";
  if (propName == WL_PROP_NAME)
    label = "wavelength";

  std::unordered_set<std::string> validUnits;
  if (propName == WL_PROP_NAME) {
    validUnits.insert("Angstrom");
    validUnits.insert("A");
  } else {
    validUnits.insert("Hz");
  }

  for (auto &name : names) {
    if (run.hasProperty(name)) {
      const std::string units = run.getProperty(name)->units();

      if (validUnits.find(units) != validUnits.end()) {
        double value = run.getLogAsSingleValue(name);
        if (value == 0.) {
          std::stringstream msg;
          msg << "'" << name << "' has a mean value of zero " << units;
          g_log.information(msg.str());
        } else {
          std::stringstream msg;
          msg << "Found " << label << " in log '" << name
              << "' with mean value " << value << " " << units;
          g_log.information(msg.str());
          return value;
        }
      } else {
        std::stringstream msg;
        msg << "When looking at " << name
            << " log encountered unknown units for " << label << ":" << units;
        g_log.warning(msg.str());
      }
    }
  }
  g_log.warning("Failed to determine " + label);
  return 0.;
}

/// Set the default values in the property manager
void PDDetermineCharacterizations::setDefaultsInPropManager() {
  if (!m_propertyManager->existsProperty("frequency")) {
    m_propertyManager->declareProperty(
        Kernel::make_unique<PropertyWithValue<double>>("frequency", 0.));
  }
  if (!m_propertyManager->existsProperty("wavelength")) {
    m_propertyManager->declareProperty(
        Kernel::make_unique<PropertyWithValue<double>>("wavelength", 0.));
  }

  if (!m_propertyManager->existsProperty("bank")) {
    m_propertyManager->declareProperty(
        Kernel::make_unique<PropertyWithValue<int>>("bank", 1));
  }

  if (!m_propertyManager->existsProperty("d_min")) {
    m_propertyManager->declareProperty(
        Kernel::make_unique<ArrayProperty<double>>("d_min"));
  }
  if (!m_propertyManager->existsProperty("d_max")) {
    m_propertyManager->declareProperty(
        Kernel::make_unique<ArrayProperty<double>>("d_max"));
  }
  if (!m_propertyManager->existsProperty("tof_min")) {
    m_propertyManager->declareProperty(
        Kernel::make_unique<PropertyWithValue<double>>("tof_min", 0.));
  }
  if (!m_propertyManager->existsProperty("tof_max")) {
    m_propertyManager->declareProperty(
        Kernel::make_unique<PropertyWithValue<double>>("tof_max", 0.));
  }
  if (!m_propertyManager->existsProperty("wavelength_min")) {
    m_propertyManager->declareProperty(
        Kernel::make_unique<PropertyWithValue<double>>("wavelength_min", 0.));
  }
  if (!m_propertyManager->existsProperty("wavelength_max")) {
    m_propertyManager->declareProperty(
        Kernel::make_unique<PropertyWithValue<double>>("wavelength_max", 0.));
  }

  if (!m_propertyManager->existsProperty("vanadium")) {
    m_propertyManager->declareProperty(
        Kernel::make_unique<ArrayProperty<int32_t>>("vanadium", "0"));
  }
  if (!m_propertyManager->existsProperty("vanadium_background")) {
    m_propertyManager->declareProperty(
        Kernel::make_unique<ArrayProperty<int32_t>>("vanadium_background",
                                                    "0"));
  }
  if (!m_propertyManager->existsProperty("container")) {
    m_propertyManager->declareProperty(
        Kernel::make_unique<ArrayProperty<int32_t>>("container", "0"));
  }
  if (!m_propertyManager->existsProperty("empty_environment")) {
    m_propertyManager->declareProperty(
        Kernel::make_unique<ArrayProperty<int32_t>>("empty_environment", "0"));
  }
  if (!m_propertyManager->existsProperty("empty_instrument")) {
    m_propertyManager->declareProperty(
        Kernel::make_unique<ArrayProperty<int32_t>>("empty_instrument", "0"));
  }
}

/**
 * Set the run number in the property manager from algoritm inputs.
 * @param inputName
 * @param propName
 */
void PDDetermineCharacterizations::overrideRunNumProperty(
    const std::string &inputName, const std::string &propName) {
  if (this->isDefault(inputName))
    return;

  std::vector<int32_t> runnumbers = this->getProperty(inputName);
  if ((!runnumbers.empty()) && (runnumbers[0] != 0)) {
    if (runnumbers[0] < 0) {
      runnumbers.erase(runnumbers.begin(), runnumbers.end());
      runnumbers.push_back(0);
    }
    m_propertyManager->setProperty(propName, runnumbers);
  }
}

/// Execute the algorithm.
void PDDetermineCharacterizations::exec() {
  // setup property manager to return
  const std::string managerName = getPropertyValue("ReductionProperties");
  if (PropertyManagerDataService::Instance().doesExist(managerName)) {
    m_propertyManager =
        PropertyManagerDataService::Instance().retrieve(managerName);
  } else {
    m_propertyManager = boost::make_shared<Kernel::PropertyManager>();
    PropertyManagerDataService::Instance().addOrReplace(managerName,
                                                        m_propertyManager);
  }

  setDefaultsInPropManager();

  m_characterizations = getProperty(CHAR_PROP_NAME);
  if (bool(m_characterizations) && (m_characterizations->rowCount() > 0)) {
    API::MatrixWorkspace_sptr inputWS = getProperty("InputWorkspace");
    auto run = inputWS->mutableRun();

    double frequency = getLogValue(run, FREQ_PROP_NAME);

    double wavelength = getLogValue(run, WL_PROP_NAME);

    // determine the container name
    std::string container;
    if (run.hasProperty("SampleContainer")) {
      const auto containerProp = run.getLogData("SampleContainer");

      // the property is normally a TimeSeriesProperty
      const auto containerPropSeries =
          dynamic_cast<TimeSeriesProperty<std::string> *>(containerProp);
      if (bool(containerPropSeries)) {
        // assume that only the first value matters
        container = containerPropSeries->valuesAsVector().front();
      } else {
        // try as a normal Property
        container = containerProp->value();
      }
      // remove whitespace from the value
      container = Kernel::Strings::replaceAll(container, " ", "");
    }

    getInformationFromTable(frequency, wavelength, container);
  }

  overrideRunNumProperty("BackRun", "container");
  overrideRunNumProperty("NormRun", "vanadium");
  overrideRunNumProperty("NormBackRun", "vanadium_background");
  overrideRunNumProperty("EmptyEnv", "empty_environment");
  overrideRunNumProperty("EmptyInstr", "empty_instrument");

  std::vector<std::string> expectedNames = getColumnNames();
  for (auto &expectedName : expectedNames) {
    if (m_propertyManager->existsProperty(expectedName)) {
      g_log.debug() << expectedName << ":"
                    << m_propertyManager->getPropertyValue(expectedName)
                    << "\n";
    } else {
      g_log.warning() << expectedName << " DOES NOT EXIST\n";
    }
  }
}

} // namespace Algorithms
} // namespace Mantid