Newer
Older
#include "MantidAlgorithms/Stitch1D.h"
#include "MantidAPI/WorkspaceProperty.h"
#include "MantidAPI/MatrixWorkspace.h"
Federico Montesino Pouzols
committed
#include "MantidAPI/WorkspaceFactory.h"
#include "MantidHistogramData/HistogramX.h"
#include "MantidHistogramData/HistogramY.h"
#include "MantidHistogramData/HistogramE.h"
#include "MantidKernel/BoundedValidator.h"
#include "MantidKernel/MultiThreaded.h"
#include "MantidKernel/PropertyWithValue.h"
#include "MantidKernel/RebinParamsValidator.h"
#include <boost/tuple/tuple.hpp>
#include <boost/format.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/math/special_functions.hpp>
#include <algorithm>
using namespace Mantid::Kernel;
using namespace Mantid::API;
using Mantid::HistogramData::HistogramY;
using Mantid::HistogramData::HistogramE;
using MinMaxTuple = boost::tuple<double, double>;
MinMaxTuple calculateXIntersection(MatrixWorkspace_sptr lhsWS,
MatrixWorkspace_sptr rhsWS) {
return MinMaxTuple(rhsWS->x(0).front(), lhsWS->x(0).back());
bool isNonzero(double i) { return (0 != i); }
namespace Mantid {
namespace Algorithms {
/**
* Range tolerance
*
* This is required for machine precision reasons. Used to adjust StartOverlap
*and EndOverlap so that they are
* inclusive of bin boundaries if they are sitting ontop of the bin boundaries.
*/
const double Stitch1D::range_tolerance = 1e-9;
// Register the algorithm into the AlgorithmFactory
DECLARE_ALGORITHM(Stitch1D)
/**
* Zero out all y and e data that is not in the region a1 to a2.
* @param a1 : Zero based bin index (first one)
* @param a2 : Zero based bin index (last one inclusive)
* @param source : Workspace providing the source data.
* @return Masked workspace.
*/
MatrixWorkspace_sptr Stitch1D::maskAllBut(int a1, int a2,
MatrixWorkspace_sptr &source) {
MatrixWorkspace_sptr product = WorkspaceFactory::Instance().create(source);
const int histogramCount = static_cast<int>(source->getNumberHistograms());
PARALLEL_FOR_IF(Kernel::threadSafe(*source, *product))
for (int i = 0; i < histogramCount; ++i) {
PARALLEL_START_INTERUPT_REGION
// Copy over the bin boundaries
const auto &sourceY = source->y(i);
const auto &sourceE = source->e(i);
// initially zero - out the data.
product->mutableY(i) = HistogramY(sourceY.size(), 0);
product->mutableE(i) = HistogramE(sourceE.size(), 0);
auto &newY = product->mutableY(i);
auto &newE = product->mutableE(i);
// Copy over the non-zero stuff
std::copy(sourceY.begin() + a1 + 1, sourceY.begin() + a2,
newY.begin() + a1 + 1);
std::copy(sourceE.begin() + a1 + 1, sourceE.begin() + a2,
newE.begin() + a1 + 1);
PARALLEL_END_INTERUPT_REGION
}
PARALLEL_CHECK_INTERUPT_REGION
return product;
}
/**
* Mask out data in the region between a1 and a2 with zeros. Operation performed
* on the original workspace
* @param a1 : start position in X
* @param a2 : end position in X
* @param source : Workspace to mask.
*/
void Stitch1D::maskInPlace(int a1, int a2, MatrixWorkspace_sptr source) {
const int histogramCount = static_cast<int>(source->getNumberHistograms());
PARALLEL_FOR_IF(Kernel::threadSafe(*source))
for (int i = 0; i < histogramCount; ++i) {
PARALLEL_START_INTERUPT_REGION
// Copy over the data
auto &sourceY = source->mutableY(i);
auto &sourceE = source->mutableE(i);
for (int i = a1; i < a2; ++i) {
sourceY[i] = 0;
sourceE[i] = 0;
PARALLEL_END_INTERUPT_REGION
}
PARALLEL_CHECK_INTERUPT_REGION
}
//----------------------------------------------------------------------------------------------
/** Initialize the algorithm's properties.
*/
void Stitch1D::init() {
declareProperty(make_unique<WorkspaceProperty<MatrixWorkspace>>(
"LHSWorkspace", "", Direction::Input),
"LHS input workspace.");
declareProperty(make_unique<WorkspaceProperty<MatrixWorkspace>>(
"RHSWorkspace", "", Direction::Input),
"RHS input workspace.");
declareProperty(make_unique<WorkspaceProperty<MatrixWorkspace>>(
"OutputWorkspace", "", Direction::Output),
declareProperty(make_unique<PropertyWithValue<double>>(
"StartOverlap", Mantid::EMPTY_DBL(), Direction::Input),
"Start overlap x-value in units of x-axis. Optional.");
declareProperty(make_unique<PropertyWithValue<double>>(
"EndOverlap", Mantid::EMPTY_DBL(), Direction::Input),
"End overlap x-value in units of x-axis. Optional.");
declareProperty(make_unique<ArrayProperty<double>>(
"Params", boost::make_shared<RebinParamsValidator>(true)),
"Rebinning Parameters. See Rebin for format. If only a "
"single value is provided, start and end are taken from "
"input workspaces.");
declareProperty(
make_unique<PropertyWithValue<bool>>("ScaleRHSWorkspace", true,
Direction::Input),
"Scaling either with respect to LHS workspace or RHS workspace");
declareProperty(make_unique<PropertyWithValue<bool>>("UseManualScaleFactor",
false, Direction::Input),
"True to use a provided value for the scale factor.");
auto manualScaleFactorValidator =
boost::make_shared<BoundedValidator<double>>();
manualScaleFactorValidator->setLower(0);
manualScaleFactorValidator->setExclusive(true);
declareProperty(make_unique<PropertyWithValue<double>>(
"ManualScaleFactor", 1.0, manualScaleFactorValidator,
Direction::Input),
"Provided value for the scale factor. Optional.");
declareProperty(make_unique<PropertyWithValue<double>>(
"OutScaleFactor", Mantid::EMPTY_DBL(), Direction::Output),
"The actual used value for the scaling factor.");
}
/**Gets the start of the overlapping region
@param intesectionMin :: The minimum possible value for the overlapping region
to inhabit
@param intesectionMax :: The maximum possible value for the overlapping region
to inhabit
@return a double contianing the start of the overlapping region
*/
double Stitch1D::getStartOverlap(const double &intesectionMin,
const double &intesectionMax) const {
Property *startOverlapProp = this->getProperty("StartOverlap");
double startOverlapVal = this->getProperty("StartOverlap");
startOverlapVal -= this->range_tolerance;
const bool startOverlapBeyondRange =
(startOverlapVal < intesectionMin) || (startOverlapVal > intesectionMax);
if (startOverlapProp->isDefault() || startOverlapBeyondRange) {
if (!startOverlapProp->isDefault() && startOverlapBeyondRange) {
char message[200];
std::sprintf(message, "StartOverlap is outside range at %0.4f, Min is "
"%0.4f, Max is %0.4f . Forced to be: %0.4f",
startOverlapVal, intesectionMin, intesectionMax,
intesectionMin);
g_log.warning(std::string(message));
startOverlapVal = intesectionMin;
std::stringstream buffer;
buffer << "StartOverlap calculated to be: " << startOverlapVal;
g_log.information(buffer.str());
}
return startOverlapVal;
}
/**Gets the end of the overlapping region
@param intesectionMin :: The minimum possible value for the overlapping region
to inhabit
@param intesectionMax :: The maximum possible value for the overlapping region
to inhabit
@return a double contianing the end of the overlapping region
*/
double Stitch1D::getEndOverlap(const double &intesectionMin,
const double &intesectionMax) const {
Property *endOverlapProp = this->getProperty("EndOverlap");
double endOverlapVal = this->getProperty("EndOverlap");
endOverlapVal += this->range_tolerance;
const bool endOverlapBeyondRange =
(endOverlapVal < intesectionMin) || (endOverlapVal > intesectionMax);
if (endOverlapProp->isDefault() || endOverlapBeyondRange) {
if (!endOverlapProp->isDefault() && endOverlapBeyondRange) {
char message[200];
std::sprintf(message, "EndOverlap is outside range at %0.4f, Min is "
"%0.4f, Max is %0.4f . Forced to be: %0.4f",
endOverlapVal, intesectionMin, intesectionMax,
intesectionMax);
g_log.warning(std::string(message));
endOverlapVal = intesectionMax;
std::stringstream buffer;
buffer << "EndOverlap calculated to be: " << endOverlapVal;
g_log.information(buffer.str());
}
return endOverlapVal;
}
/**Gets the rebinning parameters and calculates any missing values
@param lhsWS :: The left hand side input workspace
@param rhsWS :: The right hand side input workspace
@param scaleRHS :: Scale the right hand side workspace
@return a vector<double> contianing the rebinning parameters
*/
std::vector<double> Stitch1D::getRebinParams(MatrixWorkspace_sptr &lhsWS,
MatrixWorkspace_sptr &rhsWS,
const bool scaleRHS) const {
std::vector<double> inputParams = this->getProperty("Params");
Property *prop = this->getProperty("Params");
const bool areParamsDefault = prop->isDefault();
auto it = std::min_element(lhsX.begin(), lhsX.end());
const double minLHSX = *it;
it = std::max_element(rhsX.begin(), rhsX.end());
const double maxRHSX = *it;
// Calculate the step size based on the existing step size of the LHS
// workspace. That way scale factors should be reasonably maintained.
double calculatedStep = 0;
if (scaleRHS) {
// Calculate the step from the workspace that will not be scaled. The LHS
// workspace.
calculatedStep = lhsX[1] - lhsX[0];
} else {
// Calculate the step from the workspace that will not be scaled. The RHS
// workspace.
calculatedStep = rhsX[1] - rhsX[0];
calculatedParams.push_back(minLHSX);
calculatedParams.push_back(calculatedStep);
calculatedParams.push_back(maxRHSX);
result = calculatedParams;
} else {
if (inputParams.size() == 1) {
calculatedParams.push_back(minLHSX);
calculatedParams.push_back(inputParams.front()); // Use the step supplied.
calculatedParams.push_back(maxRHSX);
result = calculatedParams;
} else {
result = inputParams; // user has provided params. Use those.
}
}
return result;
}
/**Runs the Rebin Algorithm as a child and replaces special values
@param input :: The input workspace
@param params :: a vector<double> containing rebinning parameters
@return A shared pointer to the resulting MatrixWorkspace
*/
MatrixWorkspace_sptr Stitch1D::rebin(MatrixWorkspace_sptr &input,
auto rebin = this->createChildAlgorithm("Rebin");
rebin->setProperty("InputWorkspace", input);
rebin->setProperty("Params", params);
std::stringstream ssParams;
ssParams << params[0] << "," << params[1] << "," << params[2];
g_log.information("Rebinning Params: " + ssParams.str());
rebin->execute();
MatrixWorkspace_sptr outWS = rebin->getProperty("OutputWorkspace");
const int histogramCount = static_cast<int>(outWS->getNumberHistograms());
// Record special values and then mask them out as zeros. Special values are
// remembered and then replaced post processing.
PARALLEL_FOR_IF(Kernel::threadSafe(*outWS))
for (int i = 0; i < histogramCount; ++i) {
PARALLEL_START_INTERUPT_REGION
std::vector<size_t> &nanYIndexes = m_nanYIndexes[i];
std::vector<size_t> &nanEIndexes = m_nanEIndexes[i];
std::vector<size_t> &infYIndexes = m_infYIndexes[i];
std::vector<size_t> &infEIndexes = m_infEIndexes[i];
// Copy over the data
auto &sourceY = outWS->mutableY(i);
auto &sourceE = outWS->mutableE(i);
for (size_t j = 0; j < sourceY.size(); ++j) {
const double &value = sourceY[j];
const double &eValue = sourceE[j];
nanYIndexes.push_back(j);
sourceY[j] = 0;
infYIndexes.push_back(j);
sourceY[j] = 0;
nanEIndexes.push_back(j);
sourceE[j] = 0;
infEIndexes.push_back(j);
sourceE[j] = 0;
}
PARALLEL_END_INTERUPT_REGION
}
PARALLEL_CHECK_INTERUPT_REGION
/**Runs the Integration Algorithm as a child.
@param input :: The input workspace
@param start :: a double defining the start of the region to integrate
@param stop :: a double defining the end of the region to integrate
@return A shared pointer to the resulting MatrixWorkspace
*/
MatrixWorkspace_sptr Stitch1D::integration(MatrixWorkspace_sptr &input,
const double &start,
const double &stop) {
auto integration = this->createChildAlgorithm("Integration");
integration->setProperty("InputWorkspace", input);
integration->setProperty("RangeLower", start);
integration->setProperty("RangeUpper", stop);
g_log.information("Integration RangeLower: " +
boost::lexical_cast<std::string>(start));
g_log.information("Integration RangeUpper: " +
boost::lexical_cast<std::string>(stop));
integration->execute();
MatrixWorkspace_sptr outWS = integration->getProperty("OutputWorkspace");
return outWS;
}
/**Runs the WeightedMean Algorithm as a child
@param inOne :: The first input workspace
@param inTwo :: The second input workspace
@return A shared pointer to the resulting MatrixWorkspace
*/
MatrixWorkspace_sptr Stitch1D::weightedMean(MatrixWorkspace_sptr &inOne,
MatrixWorkspace_sptr &inTwo) {
auto weightedMean = this->createChildAlgorithm("WeightedMean");
weightedMean->setProperty("InputWorkspace1", inOne);
weightedMean->setProperty("InputWorkspace2", inTwo);
weightedMean->execute();
MatrixWorkspace_sptr outWS = weightedMean->getProperty("OutputWorkspace");
return outWS;
}
/**Runs the CreateSingleValuedWorkspace Algorithm as a child
@param val :: The double to convert to a single value workspace
@return A shared pointer to the resulting MatrixWorkspace
*/
MatrixWorkspace_sptr Stitch1D::singleValueWS(double val) {
auto singleValueWS =
this->createChildAlgorithm("CreateSingleValuedWorkspace");
singleValueWS->setProperty("DataValue", val);
singleValueWS->execute();
MatrixWorkspace_sptr outWS = singleValueWS->getProperty("OutputWorkspace");
return outWS;
}
/**finds the bins containing the ends of the overlapping region
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
@param startOverlap :: The start of the overlapping region
@param endOverlap :: The end of the overlapping region
@param workspace :: The workspace to determine the overlaps inside
@return a boost::tuple<int,int> containing the bin indexes of the overlaps
*/
boost::tuple<int, int>
Stitch1D::findStartEndIndexes(double startOverlap, double endOverlap,
MatrixWorkspace_sptr &workspace) {
int a1 = static_cast<int>(workspace->binIndexOf(startOverlap));
int a2 = static_cast<int>(workspace->binIndexOf(endOverlap));
if (a1 == a2) {
throw std::runtime_error("The Params you have provided for binning yield a "
"workspace in which start and end overlap appear "
"in the same bin. Make binning finer via input "
"Params.");
}
return boost::tuple<int, int>(a1, a2);
}
/**Determines if a workspace has non zero errors
@param ws :: The input workspace
@return True if there are any non-zero errors in the workspace
*/
bool Stitch1D::hasNonzeroErrors(MatrixWorkspace_sptr ws) {
int64_t ws_size = static_cast<int64_t>(ws->getNumberHistograms());
bool hasNonZeroErrors = false;
PARALLEL_FOR_IF(Kernel::threadSafe(*ws))
PARALLEL_START_INTERUPT_REGION
if (!hasNonZeroErrors) // Keep checking
auto it = std::find_if(e.begin(), e.end(), isNonzero);
if (it != e.end()) {
PARALLEL_CRITICAL(has_non_zero) {
hasNonZeroErrors =
true; // Set flag. Should not need to check any more spectra.
//----------------------------------------------------------------------------------------------
/** Execute the algorithm.
*/
void Stitch1D::exec() {
MatrixWorkspace_sptr rhsWS = this->getProperty("RHSWorkspace");
MatrixWorkspace_sptr lhsWS = this->getProperty("LHSWorkspace");
const MinMaxTuple intesectionXRegion = calculateXIntersection(lhsWS, rhsWS);
const double intersectionMin = intesectionXRegion.get<0>();
const double intersectionMax = intesectionXRegion.get<1>();
double startOverlap = getStartOverlap(intersectionMin, intersectionMax);
double endOverlap = getEndOverlap(intersectionMin, intersectionMax);
if (startOverlap > endOverlap) {
std::string message = boost::str(
boost::format("Stitch1D cannot have a StartOverlap > EndOverlap. "
"StartOverlap: %0.9f, EndOverlap: %0.9f") %
startOverlap % endOverlap);
throw std::runtime_error(message);
}
const bool scaleRHS = this->getProperty("ScaleRHSWorkspace");
MantidVec params = getRebinParams(lhsWS, rhsWS, scaleRHS);
const double &xMin = params.front();
const double &xMax = params.back();
if (std::abs(xMin - startOverlap) < 1E-6)
startOverlap = xMin;
if (std::abs(xMax - endOverlap) < 1E-6)
endOverlap = xMax;
if (startOverlap < xMin) {
std::string message = boost::str(
boost::format("Stitch1D StartOverlap is outside the available X range "
"after rebinning. StartOverlap: %10.9f, X min: %10.9f") %
startOverlap % xMin);
throw std::runtime_error(message);
}
if (endOverlap > xMax) {
std::string message = boost::str(
boost::format("Stitch1D EndOverlap is outside the available X range "
"after rebinning. EndOverlap: %10.9f, X max: %10.9f") %
endOverlap % xMax);
throw std::runtime_error(message);
}
const size_t histogramCount = rhsWS->getNumberHistograms();
m_nanYIndexes.resize(histogramCount);
m_infYIndexes.resize(histogramCount);
m_nanEIndexes.resize(histogramCount);
m_infEIndexes.resize(histogramCount);
const bool useManualScaleFactor = this->getProperty("UseManualScaleFactor");
double scaleFactor = 0;
double errorScaleFactor = 0;
MatrixWorkspace_sptr lhs, rhs;
// If the input workspaces are histograms ...
if (rhsWS->isHistogramData() && lhsWS->isHistogramData()) {
lhs = rebin(lhsWS, params);
rhs = rebin(rhsWS, params);
}
if (useManualScaleFactor) {
double manualScaleFactor = this->getProperty("ManualScaleFactor");
MatrixWorkspace_sptr manualScaleFactorWS = singleValueWS(manualScaleFactor);
if (scaleRHS)
rhs *= manualScaleFactorWS;
else
lhs *= manualScaleFactorWS;
scaleFactor = manualScaleFactor;
errorScaleFactor = manualScaleFactor;
} else {
auto rhsOverlapIntegrated = integration(rhs, startOverlap, endOverlap);
auto lhsOverlapIntegrated = integration(lhs, startOverlap, endOverlap);
MatrixWorkspace_sptr ratio;
if (scaleRHS) {
ratio = lhsOverlapIntegrated / rhsOverlapIntegrated;
rhs = rhs * ratio;
} else {
ratio = rhsOverlapIntegrated / lhsOverlapIntegrated;
lhs *= ratio;
scaleFactor = ratio->y(0).front();
errorScaleFactor = ratio->e(0).front();
if (scaleFactor < 1e-2 || scaleFactor > 1e2 || std::isnan(scaleFactor)) {
std::stringstream messageBuffer;
messageBuffer << "Stitch1D calculated scale factor is: " << scaleFactor
<< ". Check that in both input workspaces the integrated "
"overlap region is non-zero.";
g_log.warning(messageBuffer.str());
boost::tuple<int, int> startEnd =
findStartEndIndexes(startOverlap, endOverlap, lhs);
int a1 = boost::tuples::get<0>(startEnd);
int a2 = boost::tuples::get<1>(startEnd);
// Mask out everything BUT the overlap region as a new workspace.
MatrixWorkspace_sptr overlap1 = maskAllBut(a1, a2, lhs);
// Mask out everything BUT the overlap region as a new workspace.
MatrixWorkspace_sptr overlap2 = maskAllBut(a1, a2, rhs);
// Mask out everything AFTER the overlap region as a new workspace.
maskInPlace(a1 + 1, static_cast<int>(lhs->blocksize()), lhs);
// Mask out everything BEFORE the overlap region as a new workspace.
maskInPlace(0, a2, rhs);
MatrixWorkspace_sptr overlapave;
// If the input workspaces are histograms ...
if (rhsWS->isHistogramData() && lhsWS->isHistogramData()) {
if (hasNonzeroErrors(overlap1) && hasNonzeroErrors(overlap2)) {
overlapave = weightedMean(overlap1, overlap2);
} else {
g_log.information("Using un-weighted mean for Stitch1D overlap mean");
MatrixWorkspace_sptr sum = overlap1 + overlap2;
MatrixWorkspace_sptr denominator = singleValueWS(2.0);
overlapave = sum / denominator;
}
// If the input workspaces are point data ...
if (!rhsWS->isHistogramData() && !lhsWS->isHistogramData()) {
g_log.error("Missing implementation");
throw std::invalid_argument("Missing implementation");
} else {
g_log.error("Input workspaces must be both histograms or point data");
throw std::invalid_argument(
"Input workspaces must be both histograms or point data");
}
MatrixWorkspace_sptr result = lhs + overlapave + rhs;
// Provide log information about the scale factors used in the calculations.
std::stringstream messageBuffer;
messageBuffer << "Scale Factor Y is: " << scaleFactor
<< " Scale Factor E is: " << errorScaleFactor;
g_log.notice(messageBuffer.str());
setProperty("OutputWorkspace", result);
setProperty("OutScaleFactor", scaleFactor);
}
/**
* Put special values back.
* @param ws : MatrixWorkspace to resinsert special values into.
*/
void Stitch1D::reinsertSpecialValues(MatrixWorkspace_sptr ws) {
int histogramCount = static_cast<int>(ws->getNumberHistograms());
PARALLEL_FOR_IF(Kernel::threadSafe(*ws))
for (int i = 0; i < histogramCount; ++i) {
PARALLEL_START_INTERUPT_REGION
// Copy over the data
sourceY[j] = std::numeric_limits<double>::quiet_NaN();
sourceY[j] = std::numeric_limits<double>::infinity();
sourceY[j] = std::numeric_limits<double>::quiet_NaN();
sourceY[j] = std::numeric_limits<double>::infinity();