Newer
Older
#include <MantidAlgorithms/MergeRuns/SampleLogsBehaviour.h>
#include "MantidGeometry/Instrument.h"
#include "MantidKernel/TimeSeriesProperty.h"
namespace Mantid {
namespace Algorithms {
using namespace Kernel;
using namespace API;
namespace {
std::string generateDifferenceMessage(const std::string &item,
const std::string &wsName,
const std::string &wsValue,
const std::string &firstValue) {
std::stringstream stringstream;
stringstream << "Item \"" << item
<< "\" has different values in workspaces! Found: " << wsValue
<< " in workspace " << wsName
<< " but value in first workspace value was: " << firstValue
<< "." << std::endl;
return stringstream.str();
}
}
const std::string SampleLogsBehaviour::TIME_SERIES_MERGE =
"sample_logs_time_series";
const std::string SampleLogsBehaviour::LIST_MERGE = "sample_logs_list";
const std::string SampleLogsBehaviour::WARN_MERGE = "sample_logs_warn";
const std::string SampleLogsBehaviour::FAIL_MERGE = "sample_logs_fail";
const std::string SampleLogsBehaviour::WARN_MERGE_TOLERANCES =
"sample_logs_warn_tolerances";
const std::string SampleLogsBehaviour::FAIL_MERGE_TOLERANCES =
"sample_logs_fail_tolerances";
const std::string SampleLogsBehaviour::TIME_SERIES_SUFFIX = "_time_series";
const std::string SampleLogsBehaviour::LIST_SUFFIX = "_list";
/**
* Create and initialise an object that is responsbile for keeping track of the
* merge types, and performing the merge or warning/error for sample logs when
* calling MergeRuns.
*
* @param ws the base workspace that the other workspaces are merged into
* @param logger the logger from the parent algorithm
* @param sampleLogsTimeSeries a string with a comma separated list of the logs
* for the time series merge
* @param sampleLogsList a string with a comma separated list of the logs for a
* list merge
* @param sampleLogsWarn a string with a comma separated list of the logs for
* warning when different on merging
* @param sampleLogsWarnTolerances a string with a single value or comma
* separated list of values for the warning tolerances
* @param sampleLogsFail a string with a comma separated list of the logs for
* throwing an error when different on merging
* @param sampleLogsFailTolerances a string with a single value or comma
* separated list of values for the error tolerances
* @return An instance of SampleLogsBehaviour initialised with the merge types
* from the IPF and parent algorithm
*/
SampleLogsBehaviour::SampleLogsBehaviour(
const MatrixWorkspace_sptr &ws, Logger &logger,
std::string sampleLogsTimeSeries, std::string sampleLogsList,
std::string sampleLogsWarn, std::string sampleLogsWarnTolerances,
std::string sampleLogsFail, std::string sampleLogsFailTolerances)
: m_logger(logger) {
setSampleMap(m_logMap, MergeLogType::TimeSeries, sampleLogsTimeSeries, ws,
"");
setSampleMap(m_logMap, MergeLogType::List, sampleLogsList, ws, "");
setSampleMap(m_logMap, MergeLogType::Warn, sampleLogsWarn, ws,
sampleLogsWarnTolerances);
setSampleMap(m_logMap, MergeLogType::Fail, sampleLogsFail, ws,
sampleLogsFailTolerances);
SampleLogsMap instrumentMap;
this->createSampleLogsMapsFromInstrumentParams(instrumentMap, ws);
// This adds the parameters from the instrument to the main map, with any
// duplicates left as the versions in the MergeRuns arguments.
m_logMap.insert(instrumentMap.begin(), instrumentMap.end());
/**
* Extracts all of the settings from the instrument parameters, and adds them to
* a map of sample log behaviours.
*
* @param map the map to add the merge behaviours to
* @param ws the workspace with the instrument and initial map
*/
void SampleLogsBehaviour::createSampleLogsMapsFromInstrumentParams(
SampleLogsMap &map, const MatrixWorkspace_sptr &ws) {
std::string params =
ws->getInstrument()->getParameterAsString(TIME_SERIES_MERGE, false);
setSampleMap(map, MergeLogType::TimeSeries, params, ws, "", true);
params = ws->getInstrument()->getParameterAsString(LIST_MERGE, false);
setSampleMap(map, MergeLogType::List, params, ws, "", true);
params = ws->getInstrument()->getParameterAsString(WARN_MERGE, false);
std::string paramsTolerances;
paramsTolerances =
ws->getInstrument()->getParameterAsString(WARN_MERGE_TOLERANCES, false);
setSampleMap(map, MergeLogType::Warn, params, ws, paramsTolerances, true);
params = ws->getInstrument()->getParameterAsString(FAIL_MERGE, false);
paramsTolerances =
ws->getInstrument()->getParameterAsString(FAIL_MERGE_TOLERANCES, false);
setSampleMap(map, MergeLogType::Fail, params, ws, paramsTolerances, true);
/**
* This method updates the map with the sample log behaviour, and adds the new
* property to the workspace if required. if skipIfInPrimaryMap is true sample
* logs in the primary map are ignored. Throws std::invalid_argument if a sample
* log is not found.
*
* @param map the map to add the merge behaviours to
* @param mergeType an enum for the type of merge to perform
* @param params a string containing a comma separated list of the sample logs
* to merge for this behaviour
* @param ws the base workspace that the other workspaces are merged into
* @param paramsTolerances a string containing a comma spearated list of the
* tolerances for this merge behaviour (optional)
* @param skipIfInPrimaryMap wether to skip if in the member variable map
* (optional, default false)
*/
void SampleLogsBehaviour::setSampleMap(SampleLogsMap &map,
const MergeLogType &mergeType,
const std::string ¶ms,
const API::MatrixWorkspace_sptr &ws,
const std::string paramsTolerances,
bool skipIfInPrimaryMap) {
StringTokenizer tokenizer(params, ",", StringTokenizer::TOK_TRIM |
StringTokenizer::TOK_IGNORE_EMPTY);
StringTokenizer tokenizerTolerances(paramsTolerances, ",",
StringTokenizer::TOK_TRIM |
StringTokenizer::TOK_IGNORE_EMPTY);
auto tolerancesStringVector = tokenizerTolerances.asVector();
std::vector<double> tolerancesVector = createTolerancesVector(
tokenizer.asVector().size(), tolerancesStringVector);
StringTokenizer::Iterator i = tokenizer.begin();
std::vector<double>::iterator j = tolerancesVector.begin();
for (; i != tokenizer.end() && j != tolerancesVector.end(); ++i, ++j) {
auto item = *i;
auto tolerance = *j;
// TODO: switch to multi map and remove this?
if (skipIfInPrimaryMap && (m_logMap.count(item) != 0)) {
continue;
}
// TODO: switch to multi map and remove this?
if (map.count(item) != 0) {
throw std::invalid_argument(
"Error when making list of merge items, sample log \"" + item +
"\" defined more than once!");
}
std::shared_ptr<Property> prop;
prop = std::shared_ptr<Property>(ws->getLog(item)->clone());
} catch (std::invalid_argument &) {
m_logger.warning()
<< "Could not merge sample log \"" << item
<< "\", does not exist in workspace! This sample log will be ignored."
<< std::endl;
// Check if the property can be converted to a double.
// Must be the case for a time series, optional for others.
double value = 0.0;
isNumeric = setNumericValue(item, ws, value);
if (!isNumeric && mergeType == MergeLogType::TimeSeries) {
m_logger.error() << item << " could not be converted to a numeric type. "
"This sample log will be ignored."
<< std::endl;
continue;
if (mergeType == MergeLogType::TimeSeries) {
prop = addPropertyForTimeSeries(item, value, ws);
} else if (mergeType == MergeLogType::List) {
prop = addPropertyForList(item, prop->value(), ws);
map[item] = {mergeType, prop, tolerance, isNumeric};
/**
* Creates a vector of tolernaces with the same size as the number of sample
* logs for the merge type. If the number of names and tolerances is the same
* the vector is filled with the tolerances for each name. If no tolerances were
* specified all tolerances are set to -1, and if one tolerance is given all
* tolerances are set to that value.
*
* @param numberNames the number of sample log names
* @param tolerances a vector containing strings with the tolerances
* @param paramsTolerances a string containing nothing, or tolerances to use
* @return a vector of doubles of size numberNames
*/
std::vector<double> SampleLogsBehaviour::createTolerancesVector(
size_t numberNames, const std::vector<std::string> &tolerances) {
size_t numberTolerances = tolerances.size();
std::vector<double> tolerancesVector(numberNames);
if (numberNames == numberTolerances) {
std::transform(tolerances.begin(), tolerances.end(),
tolerancesVector.begin(),
[](const std::string &val) { return std::stod(val); });
} else if (tolerances.empty()) {
std::fill(tolerancesVector.begin(), tolerancesVector.end(), -1.0);
} else if (numberTolerances == 1) {
double value = std::stod(tolerances.front());
std::fill(tolerancesVector.begin(), tolerancesVector.end(), value);
} else {
throw std::invalid_argument("Invalid length of tolerances, found " +
std::to_string(numberTolerances) +
" tolerance values but " +
std::to_string(numberNames) + " names.");
}
return tolerancesVector;
}
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
/**
* Adds a property to the workspace provided for a TimeSeries merge type.
*
* @param item the name of the sample log to merge as a time series
* @param value the numeric value of the sample log in the first workspace
* @param ws the first workspace in the merge
* @return a shared pointer to the added property
*/
std::shared_ptr<Property> SampleLogsBehaviour::addPropertyForTimeSeries(
const std::string item, const double value,
const API::MatrixWorkspace_sptr &ws) {
std::shared_ptr<Property> returnProp;
try {
// See if property exists already - merging an output of MergeRuns
returnProp = std::shared_ptr<Property>(
ws->getLog(item + TIME_SERIES_SUFFIX)->clone());
} catch (std::invalid_argument &) {
// Property does not already exist, so add it setting the first entry
std::unique_ptr<Kernel::TimeSeriesProperty<double>> timeSeriesProp(
new TimeSeriesProperty<double>(item + TIME_SERIES_SUFFIX));
std::string startTime = ws->mutableRun().startTime().toISO8601String();
timeSeriesProp->addValue(startTime, value);
ws->mutableRun().addLogData(
std::unique_ptr<Kernel::Property>(std::move(timeSeriesProp)));
returnProp = std::shared_ptr<Property>(
ws->getLog(item + TIME_SERIES_SUFFIX)->clone());
}
return returnProp;
}
/**
* Adds a property to the workspace provided for a List merge type.
*
* @param item the name of the sample log to merge as a list
* @param value the string value of the sample log in the first workspace
* @param ws the first workspace in the merge
* @return a shared pointer to the added property
*/
std::shared_ptr<Property>
SampleLogsBehaviour::addPropertyForList(const std::string item,
const std::string value,
const API::MatrixWorkspace_sptr &ws) {
std::shared_ptr<Property> returnProp;
try {
// See if property exists already - merging an output of MergeRuns
returnProp =
std::shared_ptr<Property>(ws->getLog(item + LIST_SUFFIX)->clone());
} catch (std::invalid_argument &) {
ws->mutableRun().addProperty(item + LIST_SUFFIX, value);
returnProp =
std::shared_ptr<Property>(ws->getLog(item + LIST_SUFFIX)->clone());
}
return returnProp;
}
/**
* Tries to set the numeric value of a property.
*
* @param item the name of the sample log
* @param ws the first workspace in the merge
* @param value the value of the sample log (if it could be set)
* @return true if the sample log could be converted to a double, false otherwise
*/
bool SampleLogsBehaviour::setNumericValue(const std::string item,
const API::MatrixWorkspace_sptr &ws,
double &value) {
bool isNumeric;
try {
value = ws->getLogAsSingleValue(item);
isNumeric = true;
} catch (std::invalid_argument &) {
isNumeric = false;
}
return isNumeric;
}
/**
* Updates the sample logs according to the requested behaviour.
*
* @param addeeWS the workspace being merged
* @param outWS the workspace the others are merged into
*/
void SampleLogsBehaviour::mergeSampleLogs(
const MatrixWorkspace_sptr &addeeWS,
const API::MatrixWorkspace_sptr &outWS) {
for (auto item : m_logMap) {
Property *addeeWSProperty = addeeWS->getLog(item.first);
double outWSNumber = 0;
try {
addeeWSNumber = addeeWS->getLogAsSingleValue(item.first);
outWSNumber = outWS->getLogAsSingleValue(item.first);
} catch (std::invalid_argument &) {
if (item.second.isNumeric) {
throw std::invalid_argument(
item.first + " could not be converted to a numeric type");
}
}
switch (item.second.type) {
case MergeLogType::TimeSeries: {
this->updateTimeSeriesProperty(addeeWS, outWS, item.first);
case MergeLogType::List: {
this->updateListProperty(addeeWS, outWS, addeeWSProperty, item.first);
case MergeLogType::Warn:
this->checkWarnProperty(addeeWS, addeeWSProperty, item.second,
addeeWSNumber, outWSNumber, item.first);
case MergeLogType::Fail:
this->checkErrorProperty(addeeWS, addeeWSProperty, item.second,
addeeWSNumber, outWSNumber, item.first);
break;
}
}
}
/**
* Perform the update for a time series property, adding a new value to the
*existing time series property. Skipped if the time series log entry is in the
*addeeWS.
*
* @param addeeWS the workspace being merged
* @param outWS the workspace the others are merged into
* @param name the name of the property
*/
void SampleLogsBehaviour::updateTimeSeriesProperty(
const MatrixWorkspace_sptr &addeeWS, const MatrixWorkspace_sptr &outWS,
const std::string name) {
try {
// If this already exists we do not need to do anything, Time Series Logs
// are combined when adding workspaces.
addeeWS->getLog(name + TIME_SERIES_SUFFIX);
} catch (std::invalid_argument &) {
auto timeSeriesProp = outWS->mutableRun().getTimeSeriesProperty<double>(
name + TIME_SERIES_SUFFIX);
Kernel::DateAndTime startTime = addeeWS->mutableRun().startTime();
double value = addeeWS->mutableRun().getLogAsSingleValue(name);
timeSeriesProp->addValue(startTime, value);
}
}
/**
* Perform the update for a list property, appending a new value to the existing
*string. If the list log entry is in the addeeWS the list log entry is merged
*instead.
*
* @param addeeWS the workspace being merged
* @param outWS the workspace the others are merged into
* @param addeeWSProperty the relevant property in addeeWS
* @param name the name of the property
*/
void SampleLogsBehaviour::updateListProperty(
const MatrixWorkspace_sptr &addeeWS, const MatrixWorkspace_sptr &outWS,
Property *addeeWSProperty, const std::string name) {
try {
// If this already exists we combine the two strings.
auto propertyAddeeWS = addeeWS->getLog(name + LIST_SUFFIX);
auto propertyOutWS = outWS->mutableRun().getProperty(name + LIST_SUFFIX);
propertyOutWS->setValue(propertyOutWS->value() + ", " +
propertyAddeeWS->value());
} catch (std::invalid_argument &) {
auto property = outWS->mutableRun().getProperty(name + LIST_SUFFIX);
property->setValue(property->value() + ", " + addeeWSProperty->value());
/**
* Performs the check to see if a warning should be generated because logs are
*different. Performs a numeric comparison if a tolerance is set and the log is
*a number, else performs a string comparison.
*
* @param addeeWS the workspace being merged
* @param addeeWSProperty the property value of the workspace being merged
* @param behaviour the merge behaviour struct, containing information about the
*merge
* @param addeeWSNumber a double for the addeeWS value (0 if it is not numeric)
* @param outWSNumber a double for the outWS value (0 if it is not numeric)
* @param name the name of the sample log to check
*/
void SampleLogsBehaviour::checkWarnProperty(const MatrixWorkspace_sptr &addeeWS,
Property *addeeWSProperty,
const SampleLogBehaviour &behaviour,
const double addeeWSNumber,
const double outWSNumber,
const std::string name) {
if (behaviour.isNumeric && behaviour.tolerance > 0.0) {
if (std::abs(addeeWSNumber - outWSNumber) > behaviour.tolerance) {
m_logger.warning() << generateDifferenceMessage(
name, addeeWS->name(), addeeWSProperty->value(),
behaviour.property->value());
}
} else {
if (behaviour.property->value().compare(addeeWSProperty->value()) != 0) {
m_logger.warning() << generateDifferenceMessage(
name, addeeWS->name(), addeeWSProperty->value(),
behaviour.property->value());
}
}
}
/**
** Performs the check to see if an error should be generated because logs are
*different. Performs a numeric comparison if a tolerance is set and the log is
*a number, else performs a string comparison.
*
* @param addeeWS the workspace being merged
* @param addeeWSProperty the property value of the workspace being merged
* @param behaviour the merge behaviour struct, containing information about the
*merge
* @param addeeWSNumber a double for the addeeWS value (0 if it is not numeric)
* @param outWSNumber a double for the outWS value (0 if it is not numeric)
* @param name the name of the sample log to check
*/
void SampleLogsBehaviour::checkErrorProperty(
const MatrixWorkspace_sptr &addeeWS, Property *addeeWSProperty,
const SampleLogBehaviour &behaviour, const double addeeWSNumber,
const double outWSNumber, const std::string name) {
if (behaviour.isNumeric && behaviour.tolerance > 0.0) {
if (std::abs(addeeWSNumber - outWSNumber) > behaviour.tolerance) {
throw std::invalid_argument(generateDifferenceMessage(
name, addeeWS->name(), addeeWSProperty->value(),
behaviour.property->value()));
}
} else {
if (behaviour.property->value().compare(addeeWSProperty->value()) != 0) {
throw std::invalid_argument(generateDifferenceMessage(
name, addeeWS->name(), addeeWSProperty->value(),
behaviour.property->value()));
}
}
}
/**
* Set the values in the map to be the same as those in the output workspace.
*
* @param ws the merged workspace
*/
void SampleLogsBehaviour::setUpdatedSampleLogs(
const API::MatrixWorkspace_sptr &ws) {
for (auto item : m_logMap) {
std::string propertyToReset = item.first;
if (item.second.type == MergeLogType::TimeSeries) {
propertyToReset = item.first + TIME_SERIES_SUFFIX;
} else if (item.second.type == MergeLogType::List) {
propertyToReset = item.first + LIST_SUFFIX;
} else {
return;
}
Property *outWSProperty = ws->mutableRun().getProperty(propertyToReset);
item.second.property = std::shared_ptr<Property>(outWSProperty->clone());
/**
* Resets the sample logs in the workspace to the values in the map.
*
* @param ws the merged workspace to reset the sample logs for
*/
void SampleLogsBehaviour::resetSampleLogs(const API::MatrixWorkspace_sptr &ws) {
for (auto item : m_logMap) {
std::string propertyToReset = item.first;
if (item.second.type == MergeLogType::TimeSeries) {
propertyToReset = item.first + TIME_SERIES_SUFFIX;
auto property =
std::unique_ptr<Kernel::Property>(item.second.property->clone());
ws->mutableRun().addProperty(std::move(property), true);
} else if (item.second.type == MergeLogType::List) {
propertyToReset = item.first + LIST_SUFFIX;
ws->mutableRun()
.getProperty(propertyToReset)
->setValue(item.second.property->value());
} else {
return;
}