-
Simon Heybrock authored
Conflicts: Framework/Algorithms/inc/MantidAlgorithms/ConvertUnits.h Framework/Algorithms/src/ConvertUnits.cpp Framework/Algorithms/src/ConvertUnitsUsingDetectorTable.cpp
Simon Heybrock authoredConflicts: Framework/Algorithms/inc/MantidAlgorithms/ConvertUnits.h Framework/Algorithms/src/ConvertUnits.cpp Framework/Algorithms/src/ConvertUnitsUsingDetectorTable.cpp
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
ConvertUnits.cpp 32.16 KiB
//----------------------------------------------------------------------
// Includes
//----------------------------------------------------------------------
#include "MantidAlgorithms/ConvertUnits.h"
#include "MantidAPI/AlgorithmFactory.h"
#include "MantidAPI/Axis.h"
#include "MantidAPI/CommonBinsValidator.h"
#include "MantidAPI/Run.h"
#include "MantidAPI/WorkspaceFactory.h"
#include "MantidAPI/WorkspaceUnitValidator.h"
#include "MantidDataObjects/EventWorkspace.h"
#include "MantidDataObjects/Workspace2D.h"
#include "MantidGeometry/Instrument.h"
#include "MantidKernel/BoundedValidator.h"
#include "MantidKernel/CompositeValidator.h"
#include "MantidKernel/ListValidator.h"
#include "MantidKernel/UnitFactory.h"
#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <numeric>
namespace Mantid {
namespace Algorithms {
// Register with the algorithm factory
DECLARE_ALGORITHM(ConvertUnits)
using namespace Kernel;
using namespace API;
using namespace DataObjects;
using namespace HistogramData;
using boost::function;
using boost::bind;
/// Default constructor
ConvertUnits::ConvertUnits()
: Algorithm(), m_numberOfSpectra(0), m_distribution(false),
m_inputEvents(false), m_inputUnit(), m_outputUnit() {}
/// Initialisation method
void ConvertUnits::init() {
auto wsValidator = boost::make_shared<CompositeValidator>();
wsValidator->add<WorkspaceUnitValidator>();
declareProperty(make_unique<WorkspaceProperty<API::MatrixWorkspace>>(
"InputWorkspace", "", Direction::Input, wsValidator),
"Name of the input workspace");
declareProperty(make_unique<WorkspaceProperty<API::MatrixWorkspace>>(
"OutputWorkspace", "", Direction::Output),
"Name of the output workspace, can be the same as the input");
// Extract the current contents of the UnitFactory to be the allowed values of
// the Target property
declareProperty("Target", "", boost::make_shared<StringListValidator>(
UnitFactory::Instance().getKeys()),
"The name of the units to convert to (must be one of those "
"registered in\n"
"the Unit Factory)");
std::vector<std::string> propOptions{"Elastic", "Direct", "Indirect"};
declareProperty("EMode", "Elastic",
boost::make_shared<StringListValidator>(propOptions),
"The energy mode (default: elastic)");
auto mustBePositive = boost::make_shared<BoundedValidator<double>>();
mustBePositive->setLower(0.0);
declareProperty("EFixed", EMPTY_DBL(), mustBePositive,
"Value of fixed energy in meV : EI (EMode=Direct) or EF "
"(EMode=Indirect) . Must be\n"
"set if the target unit requires it (e.g. DeltaE)");
declareProperty("AlignBins", false,
"If true (default is false), rebins after conversion to "
"ensure that all spectra in the output workspace\n"
"have identical bin boundaries. This option is not "
"recommended (see "
"http://www.mantidproject.org/ConvertUnits).");
declareProperty(
"ConvertFromPointData", true,
"When checked, if the Input Workspace contains Points\n"
"the algorithm ConvertToHistogram will be run to convert\n"
"the Points to Bins. The Output Workspace will contains Bins.");
}
/** Executes the algorithm
* @throw std::runtime_error :: Thrown in the following cases:
* - If the input workspace has not had its unit set
* - If the input workspace contains Points, but ConvertFromPointData
* has not been enabled.
* - If ConvertFromPointData has been enabled, but the conversion to Bins
* or back to Points fails.
* @throw InstrumentDefinitionError If unable to calculate source-sample
* distance
*/
void ConvertUnits::exec() {
// Get the workspaces
MatrixWorkspace_sptr inputWS = getProperty("InputWorkspace");
const bool acceptPointData = getProperty("ConvertFromPointData");
bool workspaceWasConverted = false;
// we can do that before anything else, because it doesn't
// setup any blocksize, which is the one that changes with conversion
this->setupMemberVariables(inputWS);
// Check that the input workspace doesn't already have the desired unit.
if (m_inputUnit->unitID() == m_outputUnit->unitID()) {
const std::string outputWSName = getPropertyValue("OutputWorkspace");
const std::string inputWSName = getPropertyValue("InputWorkspace");
if (outputWSName == inputWSName) {
// If it does, just set the output workspace to point to the input one and
// be done.
g_log.information() << "Input workspace already has target unit ("
<< m_outputUnit->unitID()
<< "), so just pointing the output workspace "
"property to the input workspace.\n";
setProperty("OutputWorkspace",
boost::const_pointer_cast<MatrixWorkspace>(inputWS));
return;
} else {
// Clone the workspace.
IAlgorithm_sptr duplicate =
createChildAlgorithm("CloneWorkspace", 0.0, 0.6);
duplicate->initialize();
duplicate->setProperty("InputWorkspace", inputWS);
duplicate->execute();
Workspace_sptr temp = duplicate->getProperty("OutputWorkspace");
auto outputWs = boost::dynamic_pointer_cast<MatrixWorkspace>(temp);
setProperty("OutputWorkspace", outputWs);
return;
}
}
// Holder for the correctWS, because if we're converting from
// PointData a new workspace is created
MatrixWorkspace_sptr correctWS;
if (!inputWS->isHistogramData()) {
if (acceptPointData) {
workspaceWasConverted = true;
g_log.information(
"ConvertFromPointData is checked. Running ConvertToHistogram\n");
// not histogram data
// ConvertToHistogram
IAlgorithm_sptr convToHist = createChildAlgorithm("ConvertToHistogram");
convToHist->setProperty("InputWorkspace", inputWS);
convToHist->execute();
MatrixWorkspace_sptr temp = convToHist->getProperty("OutputWorkspace");
correctWS = boost::dynamic_pointer_cast<MatrixWorkspace>(temp);
if (!correctWS->isHistogramData()) {
throw std::runtime_error(
"Failed to convert workspace from Points to Bins");
}
} else {
throw std::runtime_error("Workspace contains points, you can either run "
"ConvertToHistogram on it, or set "
"ConvertFromPointData to enabled");
}
} else {
correctWS = inputWS;
}
MatrixWorkspace_sptr outputWS = executeUnitConversion(correctWS);
// If InputWorkspace contained point data, convert back
if (workspaceWasConverted) {
g_log.information(
"ConvertUnits is completed. Running ConvertToPointData.\n");
IAlgorithm_sptr convtoPoints = createChildAlgorithm("ConvertToPointData");
convtoPoints->setProperty("InputWorkspace", outputWS);
convtoPoints->execute();
MatrixWorkspace_sptr temp = convtoPoints->getProperty("OutputWorkspace");
outputWS = boost::dynamic_pointer_cast<MatrixWorkspace>(temp);
if (outputWS->isHistogramData()) {
throw std::runtime_error(
"Failed to convert workspace from Bins to Points");
}
}
// Point the output property to the right place.
// Do right at end (workspace could could change in removeUnphysicalBins or
// alignBins methods)
setProperty("OutputWorkspace", outputWS);
}
/**Executes the main part of the algorithm that handles the conversion of the
* units
* @param inputWS :: the input workspace that will be converted
* @throw std::runtime_error :: If the workspace has invalid X axis binning
* @return A pointer to a MatrixWorkspace_sptr that contains the converted units
*/
MatrixWorkspace_sptr
ConvertUnits::executeUnitConversion(const API::MatrixWorkspace_sptr inputWS) {
// A WS holding BinEdges cannot have less than 2 values, as a bin has
// 2 edges, having less than 2 values would mean that the WS contains Points
if (inputWS->x(0).size() < 2) {
std::stringstream msg;
msg << "Input workspace has invalid X axis binning parameters. Should "
"have "
"at least 2 values. Found " << inputWS->x(0).size() << ".";
throw std::runtime_error(msg.str());
}
if (inputWS->x(0).front() > inputWS->x(0).back() ||
inputWS->x(m_numberOfSpectra / 2).front() >
inputWS->x(m_numberOfSpectra / 2).back())
throw std::runtime_error("Input workspace has invalid X axis binning "
"parameters. X values should be increasing.");
MatrixWorkspace_sptr outputWS;
// Check whether there is a quick conversion available
double factor, power;
if (m_inputUnit->quickConversion(*m_outputUnit, factor, power))
// If test fails, could also check whether a quick conversion in the
// opposite
// direction has been entered
{
outputWS = this->convertQuickly(inputWS, factor, power);
} else {
outputWS = this->convertViaTOF(m_inputUnit, inputWS);
}
// If the units conversion has flipped the ascending direction of X, reverse
// all the vectors
if (!outputWS->x(0).empty() &&
(outputWS->x(0).front() > outputWS->x(0).back() ||
outputWS->x(m_numberOfSpectra / 2).front() >
outputWS->x(m_numberOfSpectra / 2).back())) {
this->reverse(outputWS);
}
// Need to lop bins off if converting to energy transfer.
// Don't do for EventWorkspaces, where you can easily rebin to recover the
// situation without losing information
/* This is an ugly test - could be made more general by testing for DBL_MAX
values at the ends of all spectra, but that would be less efficient */
if (m_outputUnit->unitID().find("Delta") == 0 && !m_inputEvents)
outputWS = this->removeUnphysicalBins(outputWS);
// Rebin the data to common bins if requested, and if necessary
bool alignBins = getProperty("AlignBins");
if (alignBins && !WorkspaceHelpers::commonBoundaries(outputWS))
outputWS = this->alignBins(outputWS);
// If appropriate, put back the bin width division into Y/E.
if (m_distribution && !m_inputEvents) // Never do this for event workspaces
{
this->putBackBinWidth(outputWS);
}
return outputWS;
}
/** Initialise the member variables
* @param inputWS The input workspace
*/
void ConvertUnits::setupMemberVariables(
const API::MatrixWorkspace_const_sptr inputWS) {
m_numberOfSpectra = inputWS->getNumberHistograms();
// In the context of this algorithm, we treat things as a distribution if
// the
// flag is set
// AND the data are not dimensionless
m_distribution = inputWS->isDistribution() && !inputWS->YUnit().empty();
// Check if its an event workspace
m_inputEvents =
(boost::dynamic_pointer_cast<const EventWorkspace>(inputWS) != nullptr);
m_inputUnit = inputWS->getAxis(0)->unit();
const std::string targetUnit = getPropertyValue("Target");
m_outputUnit = UnitFactory::Instance().create(targetUnit);
}
/** Create an output workspace of the appropriate (histogram or event) type
* and
* copy over the data
* @param inputWS The input workspace
*/
API::MatrixWorkspace_sptr ConvertUnits::setupOutputWorkspace(
const API::MatrixWorkspace_const_sptr inputWS) {
MatrixWorkspace_sptr outputWS = getProperty("OutputWorkspace");
// If input and output workspaces are NOT the same, create a new workspace
// for
// the output
if (outputWS != inputWS) {
outputWS = inputWS->clone();
}
if (!m_inputEvents && m_distribution) {
// Loop over the histograms (detector spectra)
Progress prog(this, 0.0, 0.2, m_numberOfSpectra);
PARALLEL_FOR1(outputWS)
for (int64_t i = 0; i < static_cast<int64_t>(m_numberOfSpectra); ++i) {
PARALLEL_START_INTERUPT_REGION
// Take the bin width dependency out of the Y & E data
auto &X = outputWS->x(i);
auto &Y = outputWS->mutableY(i);
auto &E = outputWS->mutableE(i);
for (size_t j = 0; j < outputWS->blocksize(); ++j) {
const double width = std::abs(X[j + 1] - X[j]);
Y[j] *= width;
E[j] *= width;
}
prog.report("Convert to " + m_outputUnit->unitID());
PARALLEL_END_INTERUPT_REGION
}
PARALLEL_CHECK_INTERUPT_REGION
}
// Set the final unit that our output workspace will have
outputWS->getAxis(0)->unit() = m_outputUnit;
storeEModeOnWorkspace(outputWS);
return outputWS;
}
/** Stores the emode in the provided workspace
* @param outputWS The workspace
*/
void ConvertUnits::storeEModeOnWorkspace(API::MatrixWorkspace_sptr outputWS) {
// Store the emode
const bool overwrite(true);
outputWS->mutableRun().addProperty("deltaE-mode", getPropertyValue("EMode"),
overwrite);
}
/** Convert the workspace units according to a simple output = a * (input^b)
* relationship
* @param inputWS :: the input workspace
* @param factor :: the conversion factor a to apply
* @param power :: the Power b to apply to the conversion
* @returns A shared pointer to the output workspace
*/
MatrixWorkspace_sptr
ConvertUnits::convertQuickly(API::MatrixWorkspace_const_sptr inputWS,
const double &factor, const double &power) {
Progress prog(this, 0.2, 1.0, m_numberOfSpectra);
int64_t numberOfSpectra_i =
static_cast<int64_t>(m_numberOfSpectra); // cast to make openmp happy
// create the output workspace
MatrixWorkspace_sptr outputWS = this->setupOutputWorkspace(inputWS);
// See if the workspace has common bins - if so the X vector can be common
// First a quick check using the validator
CommonBinsValidator sameBins;
bool commonBoundaries = false;
if (sameBins.isValid(inputWS) == "") {
commonBoundaries = WorkspaceHelpers::commonBoundaries(inputWS);
// Only do the full check if the quick one passes
if (commonBoundaries) {
// Calculate the new (common) X values
for (auto &x : outputWS->mutableX(0)) {
x = factor * std::pow(x, power);
}
auto xVals = outputWS->sharedX(0);
PARALLEL_FOR1(outputWS)
for (int64_t j = 1; j < numberOfSpectra_i; ++j) {
PARALLEL_START_INTERUPT_REGION
outputWS->setX(j, xVals);
prog.report("Convert to " + m_outputUnit->unitID());
PARALLEL_END_INTERUPT_REGION
}
PARALLEL_CHECK_INTERUPT_REGION
if (!m_inputEvents) // if in event mode the work is done
return outputWS;
}
}
EventWorkspace_sptr eventWS =
boost::dynamic_pointer_cast<EventWorkspace>(outputWS);
assert(static_cast<bool>(eventWS) == m_inputEvents); // Sanity check
// If we get to here then the bins weren't aligned and each spectrum is
// unique
// Loop over the histograms (detector spectra)
PARALLEL_FOR1(outputWS)
for (int64_t k = 0; k < numberOfSpectra_i; ++k) {
PARALLEL_START_INTERUPT_REGION
if (!commonBoundaries) {
for (auto &x : outputWS->mutableX(k)) {
x = factor * std::pow(x, power);
}
}
// Convert the events themselves if necessary.
if (m_inputEvents) {
eventWS->getSpectrum(k).convertUnitsQuickly(factor, power);
}
prog.report("Convert to " + m_outputUnit->unitID());
PARALLEL_END_INTERUPT_REGION
}
PARALLEL_CHECK_INTERUPT_REGION
if (m_inputEvents)
eventWS->clearMRU();
return outputWS;
}
/** Get the L2, theta and efixed values for a workspace index
* @param outputUnit :: The output unit
* @param source :: The source of the instrument
* @param sample :: The sample of the instrument
* @param l1 :: The source to sample distance
* @param emode :: The energy mode
* @param ws :: The workspace
* @param thetaFunction :: The function to calculate twotheta
* @param wsIndex :: The workspace index
* @param efixed :: the returned fixed energy
* @param l2 :: The returned sample - detector distance
* @param twoTheta :: the returned two theta angle
* @returns true if lookup successful, false on error
*/
bool ConvertUnits::getDetectorValues(
const Kernel::Unit &outputUnit, const Geometry::IComponent &source,
const Geometry::IComponent &sample, double l1, int emode,
const MatrixWorkspace &ws,
function<double(const Geometry::IDetector &)> thetaFunction,
int64_t wsIndex, double &efixed, double &l2, double &twoTheta) {
try {
Geometry::IDetector_const_sptr det = ws.getDetector(wsIndex);
// Get the sample-detector distance for this detector (in metres)
if (!det->isMonitor()) {
l2 = det->getDistance(sample);
// The scattering angle for this detector (in radians).
twoTheta = thetaFunction(*det);
// If an indirect instrument, try getting Efixed from the geometry
if (emode == 2) // indirect
{
if (efixed == EMPTY_DBL()) {
try {
// Get the parameter map
Geometry::Parameter_sptr par =
ws.constInstrumentParameters().getRecursive(det.get(),
"Efixed");
if (par) {
efixed = par->value<double>();
g_log.debug() << "Detector: " << det->getID()
<< " EFixed: " << efixed << "\n";
}
} catch (std::runtime_error &) { /* Throws if a DetectorGroup, use
single
provided value */
}
}
}
} else // If this is a monitor then make l1+l2 = source-detector distance
// and twoTheta=0
{
l2 = det->getDistance(source);
l2 = l2 - l1;
twoTheta = 0.0;
efixed = DBL_MIN;
// Energy transfer is meaningless for a monitor, so set l2 to 0.
if (outputUnit.unitID().find("DeltaE") != std::string::npos) {
l2 = 0.0;
}
}
} catch (Exception::NotFoundError &) {
return false;
}
return true;
}
/** Convert the workspace units using TOF as an intermediate step in the
* conversion
* @param fromUnit :: The unit of the input workspace
* @param inputWS :: The input workspace
* @returns A shared pointer to the output workspace
*/
MatrixWorkspace_sptr
ConvertUnits::convertViaTOF(Kernel::Unit_const_sptr fromUnit,
API::MatrixWorkspace_const_sptr inputWS) {
using namespace Geometry;
Progress prog(this, 0.2, 1.0, m_numberOfSpectra);
int64_t numberOfSpectra_i =
static_cast<int64_t>(m_numberOfSpectra); // cast to make openmp happy
// Get a pointer to the instrument contained in the workspace
Instrument_const_sptr instrument = inputWS->getInstrument();
Kernel::Unit_const_sptr outputUnit = m_outputUnit;
// Get the distance between the source and the sample (assume in metres)
IComponent_const_sptr source = instrument->getSource();
IComponent_const_sptr sample = instrument->getSample();
if (source == nullptr || sample == nullptr) {
throw Exception::InstrumentDefinitionError("Instrument not sufficiently "
"defined: failed to get source "
"and/or sample");
}
double l1;
try {
l1 = source->getDistance(*sample);
g_log.debug() << "Source-sample distance: " << l1 << '\n';
} catch (Exception::NotFoundError &) {
g_log.error("Unable to calculate source-sample distance");
throw Exception::InstrumentDefinitionError(
"Unable to calculate source-sample distance", inputWS->getTitle());
}
int failedDetectorCount = 0;
/// @todo No implementation for any of these in the geometry yet so using
/// properties
const std::string emodeStr = getProperty("EMode");
// Convert back to an integer representation
int emode = 0;
if (emodeStr == "Direct")
emode = 1;
else if (emodeStr == "Indirect")
emode = 2;
// Not doing anything with the Y vector in to/fromTOF yet, so just pass
// empty
// vector
std::vector<double> emptyVec;
const bool needEfixed =
(outputUnit->unitID().find("DeltaE") != std::string::npos ||
outputUnit->unitID().find("Wave") != std::string::npos);
double efixedProp = getProperty("Efixed");
if (emode == 1) {
//... direct efixed gather
if (efixedProp == EMPTY_DBL()) {
// try and get the value from the run parameters
const API::Run &run = inputWS->run();
if (run.hasProperty("Ei")) {
Kernel::Property *prop = run.getProperty("Ei");
efixedProp = boost::lexical_cast<double, std::string>(prop->value());
} else {
if (needEfixed) {
throw std::invalid_argument(
"Could not retrieve incident energy from run object");
} else {
efixedProp = 0.0;
}
}
}
} else if (emode == 0 && efixedProp == EMPTY_DBL()) // Elastic
{
efixedProp = 0.0;
}
std::vector<std::string> parameters =
inputWS->getInstrument()->getStringParameter("show-signed-theta");
bool bUseSignedVersion =
(!parameters.empty()) &&
find(parameters.begin(), parameters.end(), "Always") != parameters.end();
function<double(const IDetector &)> thetaFunction =
bUseSignedVersion
? bind(&MatrixWorkspace::detectorSignedTwoTheta, inputWS, _1)
: bind(&MatrixWorkspace::detectorTwoTheta, inputWS, _1);
// Perform Sanity Validation before creating workspace
double checkefixed = efixedProp;
double checkl2;
double checktwoTheta;
size_t checkIndex = 0;
if (getDetectorValues(*outputUnit, *source, *sample, l1, emode, *inputWS,
thetaFunction, checkIndex, checkefixed, checkl2,
checktwoTheta)) {
const double checkdelta = 0.0;
// copy the X values for the check
auto checkXValues = inputWS->readX(checkIndex);
// Convert the input unit to time-of-flight
auto checkFromUnit = std::unique_ptr<Unit>(fromUnit->clone());
auto checkOutputUnit = std::unique_ptr<Unit>(outputUnit->clone());
checkFromUnit->toTOF(checkXValues, emptyVec, l1, checkl2, checktwoTheta,
emode, checkefixed, checkdelta);
// Convert from time-of-flight to the desired unit
checkOutputUnit->fromTOF(checkXValues, emptyVec, l1, checkl2, checktwoTheta,
emode, checkefixed, checkdelta);
}
// create the output workspace
MatrixWorkspace_sptr outputWS = this->setupOutputWorkspace(inputWS);
EventWorkspace_sptr eventWS =
boost::dynamic_pointer_cast<EventWorkspace>(outputWS);
assert(static_cast<bool>(eventWS) == m_inputEvents); // Sanity check
// Loop over the histograms (detector spectra)
for (int64_t i = 0; i < numberOfSpectra_i; ++i) {
double efixed = efixedProp;
// Now get the detector object for this histogram
double l2;
double twoTheta;
if (getDetectorValues(*outputUnit, *source, *sample, l1, emode, *outputWS,
thetaFunction, i, efixed, l2, twoTheta)) {
// Make local copies of the units. This allows running the loop in
// parallel
auto localFromUnit = std::unique_ptr<Unit>(fromUnit->clone());
auto localOutputUnit = std::unique_ptr<Unit>(outputUnit->clone());
/// @todo Don't yet consider hold-off (delta)
const double delta = 0.0;
// TODO toTOF and fromTOF need to be reimplemented outside of kernel
localFromUnit->toTOF(outputWS->dataX(i), emptyVec, l1, l2, twoTheta,
emode, efixed, delta);
// Convert from time-of-flight to the desired unit
localOutputUnit->fromTOF(outputWS->dataX(i), emptyVec, l1, l2, twoTheta,
emode, efixed, delta);
// EventWorkspace part, modifying the EventLists.
if (m_inputEvents) {
eventWS->getSpectrum(i)
.convertUnitsViaTof(localFromUnit.get(), localOutputUnit.get());
}
} else {
// Get to here if exception thrown when calculating distance to detector
failedDetectorCount++;
// Since you usually (always?) get to here when there's no attached
// detectors, this call is
// the same as just zeroing out the data (calling clearData on the
// spectrum)
outputWS->maskWorkspaceIndex(i);
}
prog.report("Convert to " + m_outputUnit->unitID());
} // loop over spectra
if (failedDetectorCount != 0) {
g_log.information() << "Unable to calculate sample-detector distance for "
<< failedDetectorCount
<< " spectra. Masking spectrum.\n";
}
if (m_inputEvents)
eventWS->clearMRU();
if (emode == 1) {
//... direct efixed gather
if (efixedProp != EMPTY_DBL()) {
// set the Ei value in the run parameters
API::Run &run = outputWS->mutableRun();
run.addProperty<double>("Ei", efixedProp, true);
}
}
return outputWS;
}
/// Calls Rebin as a Child Algorithm to align the bins
API::MatrixWorkspace_sptr
ConvertUnits::alignBins(API::MatrixWorkspace_sptr workspace) {
// Create a Rebin child algorithm
IAlgorithm_sptr childAlg = createChildAlgorithm("Rebin");
childAlg->setProperty<MatrixWorkspace_sptr>("InputWorkspace", workspace);
// Next line for EventWorkspaces - needed for as long as in/out set same
// keeps
// as events.
childAlg->setProperty<MatrixWorkspace_sptr>("OutputWorkspace", workspace);
childAlg->setProperty<std::vector<double>>(
"Params", this->calculateRebinParams(workspace));
childAlg->executeAsChildAlg();
return childAlg->getProperty("OutputWorkspace");
}
/// The Rebin parameters should cover the full range of the converted unit,
/// with
/// the same number of bins
const std::vector<double> ConvertUnits::calculateRebinParams(
const API::MatrixWorkspace_const_sptr workspace) const {
// Need to loop round and find the full range
double XMin = DBL_MAX, XMax = DBL_MIN;
const size_t numSpec = workspace->getNumberHistograms();
for (size_t i = 0; i < numSpec; ++i) {
try {
Geometry::IDetector_const_sptr det = workspace->getDetector(i);
if (!det->isMasked()) {
auto &XData = workspace->x(i);
double xfront = XData.front();
double xback = XData.back();
if (boost::math::isfinite(xfront) && boost::math::isfinite(xback)) {
if (xfront < XMin)
XMin = xfront;
if (xback > XMax)
XMax = xback;
}
}
} catch (Exception::NotFoundError &) {
} // Do nothing
}
const double step =
(XMax - XMin) / static_cast<double>(workspace->blocksize());
return {XMin, step, XMax};
}
/** Reverses the workspace if X values are in descending order
* @param WS The workspace to operate on
*/
void ConvertUnits::reverse(API::MatrixWorkspace_sptr WS) {
EventWorkspace_sptr eventWS = boost::dynamic_pointer_cast<EventWorkspace>(WS);
bool isInputEvents = static_cast<bool>(eventWS);
size_t numberOfSpectra = WS->getNumberHistograms();
if (WorkspaceHelpers::commonBoundaries(WS) && !isInputEvents) {
auto reverseX = make_cow<HistogramData::HistogramX>(WS->x(0).crbegin(),
WS->x(0).crend());
for (size_t j = 0; j < numberOfSpectra; ++j) {
WS->setSharedX(j, reverseX);
std::reverse(WS->dataY(j).begin(), WS->dataY(j).end());
std::reverse(WS->dataE(j).begin(), WS->dataE(j).end());
if (j % 100 == 0)
interruption_point();
}
} else {
// either events or ragged boundaries
int numberOfSpectra_i = static_cast<int>(numberOfSpectra);
PARALLEL_FOR1(WS)
for (int j = 0; j < numberOfSpectra_i; ++j) {
PARALLEL_START_INTERUPT_REGION
if (isInputEvents) {
eventWS->getSpectrum(j).reverse();
} else {
std::reverse(WS->mutableX(j).begin(), WS->mutableX(j).end());
std::reverse(WS->mutableY(j).begin(), WS->mutableY(j).end());
std::reverse(WS->mutableE(j).begin(), WS->mutableE(j).end());
}
PARALLEL_END_INTERUPT_REGION
}
PARALLEL_CHECK_INTERUPT_REGION
}
}
/** Unwieldy method which removes bins which lie in a physically inaccessible
* region.
* This presently only occurs in conversions to energy transfer, where the
* initial
* unit conversion sets them to +/-DBL_MAX. This method removes those bins,
* leading
* to a workspace which is smaller than the input one.
* As presently implemented, it unfortunately requires testing for and
* knowledge of
* aspects of the particular units conversion instead of keeping all that in
* the
* units class. It could be made more general, but that would be less
* efficient.
* @param workspace :: The workspace after initial unit conversion
* @return The workspace after bins have been removed
*/
API::MatrixWorkspace_sptr ConvertUnits::removeUnphysicalBins(
const Mantid::API::MatrixWorkspace_const_sptr workspace) {
MatrixWorkspace_sptr result;
const size_t numSpec = workspace->getNumberHistograms();
const std::string emode = getProperty("Emode");
if (emode == "Direct") {
// First the easy case of direct instruments, where all spectra will need
// the
// same number of bins removed
// Need to make sure we don't pick a monitor as the 'reference' X spectrum
// (X0)
size_t i = 0;
for (; i < numSpec; ++i) {
try {
Geometry::IDetector_const_sptr det = workspace->getDetector(i);
if (!det->isMonitor())
break;
} catch (Exception::NotFoundError &) { /* Do nothing */
}
}
// Get an X spectrum to search (they're all the same, monitors excepted)
auto &X0 = workspace->x(i);
auto start = std::lower_bound(X0.cbegin(), X0.cend(), -1.0e-10 * DBL_MAX);
if (start == X0.end()) {
const std::string e("Check the input EFixed: the one given leads to all "
"bins being in the physically inaccessible region.");
g_log.error(e);
throw std::invalid_argument(e);
}
MantidVec::difference_type bins = X0.cend() - start;
MantidVec::difference_type first = start - X0.cbegin();
result =
WorkspaceFactory::Instance().create(workspace, numSpec, bins, bins - 1);
for (size_t i = 0; i < numSpec; ++i) {
auto &X = workspace->x(i);
auto &Y = workspace->y(i);
auto &E = workspace->e(i);
result->mutableX(i).assign(X.begin() + first, X.end());
result->mutableY(i).assign(Y.begin() + first, Y.end());
result->mutableE(i).assign(E.begin() + first, E.end());
}
} else if (emode == "Indirect") {
// Now the indirect instruments. In this case we could want to keep a
// different
// number of bins in each spectrum because, in general L2 is different for
// each
// one.
// Thus, we first need to loop to find largest 'good' range
std::vector<MantidVec::difference_type> lastBins(numSpec);
int maxBins = 0;
for (size_t i = 0; i < numSpec; ++i) {
const MantidVec &X = workspace->readX(i);
auto end = std::lower_bound(X.cbegin(), X.cend(), 1.0e-10 * DBL_MAX);
MantidVec::difference_type bins = end - X.cbegin();
lastBins[i] = bins;
if (bins > maxBins)
maxBins = static_cast<int>(bins);
}
g_log.debug() << maxBins << '\n';
// Now create an output workspace large enough for the longest 'good'
// range
result = WorkspaceFactory::Instance().create(workspace, numSpec, maxBins,
maxBins - 1);
// Next, loop again copying in the correct range for each spectrum
for (int64_t j = 0; j < int64_t(numSpec); ++j) {
auto edges = workspace->binEdges(j);
auto k = lastBins[j];
result->mutableX(j).assign(edges.cbegin(), edges.cbegin() + k);
// If the entire X range is not covered, generate fake values.
if (k < maxBins) {
std::iota(result->mutableX(j).begin() + k, result->mutableX(j).end(),
workspace->x(j)[k] + 1);
}
result->mutableY(j)
.assign(workspace->y(j).cbegin(), workspace->y(j).cbegin() + (k - 1));
result->mutableE(j)
.assign(workspace->e(j).cbegin(), workspace->e(j).cbegin() + (k - 1));
}
}
return result;
}
/** Divide by the bin width if workspace is a distribution
* @param outputWS The workspace to operate on
*/
void ConvertUnits::putBackBinWidth(const API::MatrixWorkspace_sptr outputWS) {
const size_t outSize = outputWS->blocksize();
for (size_t i = 0; i < m_numberOfSpectra; ++i) {
for (size_t j = 0; j < outSize; ++j) {
const double width = std::abs(outputWS->x(i)[j + 1] - outputWS->x(i)[j]);
outputWS->mutableY(i)[j] = outputWS->y(i)[j] / width;
outputWS->mutableE(i)[j] = outputWS->e(i)[j] / width;
}
}
}
} // namespace Algorithm
} // namespace Mantid