Newer
Older
//----------------------------------------------------------------------
// Includes
//----------------------------------------------------------------------
#include "MantidAlgorithms/ConvertUnits.h"
Russell Taylor
committed
#include "MantidAPI/WorkspaceValidators.h"
#include "MantidAPI/AlgorithmFactory.h"
#include "MantidKernel/UnitFactory.h"
Russell Taylor
committed
#include "MantidDataObjects/Workspace2D.h"
Russell Taylor
committed
#include <cfloat>
#include <iostream>
namespace Mantid
{
namespace Algorithms
{
// Register with the algorithm factory
DECLARE_ALGORITHM(ConvertUnits)
using namespace Kernel;
using namespace API;
Russell Taylor
committed
using namespace DataObjects;
// Get a reference to the logger
Logger& ConvertUnits::g_log = Logger::get("ConvertUnits");
/// Default constructor
ConvertUnits::ConvertUnits() : Algorithm()
{
}
/// Destructor
ConvertUnits::~ConvertUnits()
{
}
/// Initialisation method
void ConvertUnits::init()
{
Russell Taylor
committed
CompositeValidator<> *wsValidator = new CompositeValidator<>;
wsValidator->add(new WorkspaceUnitValidator<>);
wsValidator->add(new HistogramValidator<>);
Russell Taylor
committed
declareProperty(new WorkspaceProperty<API::Workspace>("InputWorkspace","",Direction::Input,wsValidator));
Russell Taylor
committed
declareProperty(new WorkspaceProperty<API::Workspace>("OutputWorkspace","",Direction::Output));
// Extract the current contents of the UnitFactory to be the allowed values of the Target property
declareProperty("Target","",new ListValidator(UnitFactory::Instance().getKeys()) );
declareProperty("Emode",0,new BoundedValidator<int>(0,2));
BoundedValidator<double> *mustBePositive = new BoundedValidator<double>();
mustBePositive->setLower(0.0);
declareProperty("Efixed",0.0,mustBePositive);
Russell Taylor
committed
declareProperty("AlignBins",false);
}
/** Executes the algorithm
Russell Taylor
committed
* @throw std::runtime_error If the input workspace has not had its unit set
* @throw NotImplementedError If the input workspace contains point (not histogram) data
* @throw InstrumentDefinitionError If unable to calculate source-sample distance
*/
void ConvertUnits::exec()
{
Russell Taylor
committed
// Get the workspaces
Russell Taylor
committed
API::Workspace_const_sptr inputWS = getProperty("InputWorkspace");
API::Workspace_sptr outputWS = getProperty("OutputWorkspace");
// Check that the input workspace doesn't already have the desired unit.
// If it does, just set the output workspace to point to the input one.
boost::shared_ptr<Unit> inputUnit = inputWS->getAxis(0)->unit();
const std::string targetUnit = getPropertyValue("Target");
if ( inputUnit->unitID() == targetUnit )
{
g_log.information() << "Input workspace already has target unit (" << targetUnit
<< "), so just pointing the output workspace property to the input workspace." << std::endl;
setProperty("OutputWorkspace",boost::const_pointer_cast<Workspace>(inputWS));
return;
}
// If input and output workspaces are not the same, create a new workspace for the output
if (outputWS != inputWS )
{
outputWS = WorkspaceFactory::Instance().create(inputWS);
setProperty("OutputWorkspace",outputWS);
}
Russell Taylor
committed
// Set the final unit that our output workspace will have
Russell Taylor
committed
boost::shared_ptr<Unit> outputUnit = outputWS->getAxis(0)->unit() = UnitFactory::Instance().create(targetUnit);
// Check whether the Y data of the input WS is dimensioned and set output WS flag to be same
Russell Taylor
committed
const bool distribution = outputWS->isDistribution(inputWS->isDistribution());
const unsigned int size = inputWS->blocksize();
Russell Taylor
committed
// Calculate the number of spectra in this workspace
const int numberOfSpectra = inputWS->size() / size;
int iprogress_step = numberOfSpectra / 100;
if (iprogress_step == 0) iprogress_step = 1;
Russell Taylor
committed
// Loop over the histograms (detector spectra)
for (int i = 0; i < numberOfSpectra; ++i) {
Russell Taylor
committed
Russell Taylor
committed
// Take the bin width dependency out of the Y & E data
if (distribution)
{
for (unsigned int j = 0; j < size; ++j)
{
const double width = std::abs( inputWS->dataX(i)[j+1] - inputWS->dataX(i)[j] );
outputWS->dataY(i)[j] = inputWS->dataY(i)[j]*width;
outputWS->dataE(i)[j] = inputWS->dataE(i)[j]*width;
}
}
else
{
// Just copy over
outputWS->dataY(i) = inputWS->dataY(i);
Russell Taylor
committed
outputWS->dataE(i) = inputWS->dataE(i);
Russell Taylor
committed
}
// Copy over the X data (no copying will happen if the two workspaces are the same)
outputWS->dataX(i) = inputWS->dataX(i);
{
progress( double(i)/numberOfSpectra/2 );
interruption_point();
}
Russell Taylor
committed
}
Russell Taylor
committed
// Check whether there is a quick conversion available
double factor, power;
Russell Taylor
committed
if ( inputUnit->quickConversion(*outputUnit,factor,power) )
Russell Taylor
committed
// If test fails, could also check whether a quick conversion in the opposite direction has been entered
{
convertQuickly(numberOfSpectra,outputWS,factor,power);
}
else
{
convertViaTOF(numberOfSpectra,inputWS,outputWS);
}
Russell Taylor
committed
Russell Taylor
committed
// Rebin the data to common bins if requested, and if necessary
bool alignBins = getProperty("AlignBins");
if (alignBins && !WorkspaceHelpers::commonBoundaries(outputWS))
{
outputWS = this->alignBins(outputWS);
setProperty("OutputWorkspace",outputWS);
}
Russell Taylor
committed
// If appropriate, put back the bin width division into Y/E.
if (distribution)
{
for (int i = 0; i < numberOfSpectra; ++i) {
// There must be good case for having a 'divideByBinWidth'/'normalise' algorithm...
for (unsigned int j = 0; j < size; ++j)
{
const double width = std::abs( outputWS->dataX(i)[j+1] - outputWS->dataX(i)[j] );
outputWS->dataY(i)[j] = inputWS->dataY(i)[j]/width;
outputWS->dataE(i)[j] = inputWS->dataE(i)[j]/width;
}
}
}
Russell Taylor
committed
Russell Taylor
committed
}
/** Convert the workspace units according to a simple output = a * (input^b) relationship
Russell Taylor
committed
* @param numberOfSpectra The number of Spectra
* @param outputWS the output workspace
* @param factor the conversion factor a to apply
* @param power the Power b to apply to the conversion
*/
Russell Taylor
committed
void ConvertUnits::convertQuickly(const int& numberOfSpectra, API::Workspace_sptr outputWS, const double& factor, const double& power)
{
Russell Taylor
committed
// See if the workspace has common bins - if so the X vector can be common
// First a quick check using the validator
CommonBinsValidator<> sameBins;
if ( sameBins.isValid(outputWS) )
{
// Only do the full check if the quick one passes
if ( WorkspaceHelpers::commonBoundaries(outputWS) )
{
// Calculate the new (common) X values
std::vector<double>::iterator iter;
for (iter = outputWS->dataX(0).begin(); iter != outputWS->dataX(0).end(); ++iter)
{
*iter = factor * std::pow(*iter,power);
}
// If this is a Workspace2D then loop over the other spectra passing in the pointer
Workspace2D_sptr WS2D = boost::dynamic_pointer_cast<Workspace2D>(outputWS);
if (WS2D)
{
Histogram1D::RCtype xVals;
xVals.access() = outputWS->dataX(0);
for (int j = 1; j < numberOfSpectra; ++j)
{
WS2D->setX(j,xVals);
if ( j % 100 == 0)
{
progress( 0.5 + double(j)/numberOfSpectra/2 );
interruption_point();
}
Russell Taylor
committed
}
}
return;
}
}
// If we get to here then the bins weren't aligned and each spectrum is unique
Russell Taylor
committed
// Loop over the histograms (detector spectra)
Russell Taylor
committed
for (int k = 0; k < numberOfSpectra; ++k) {
Russell Taylor
committed
std::vector<double>::iterator it;
Russell Taylor
committed
for (it = outputWS->dataX(k).begin(); it != outputWS->dataX(k).end(); ++it)
Russell Taylor
committed
{
*it = factor * std::pow(*it,power);
}
}
Russell Taylor
committed
return;
Russell Taylor
committed
}
/** Convert the workspace units using TOF as an intermediate step in the conversion
Russell Taylor
committed
* @param numberOfSpectra The number of Spectra
* @param inputWS the input workspace
* @param outputWS the output workspace
*/
Russell Taylor
committed
void ConvertUnits::convertViaTOF(const int& numberOfSpectra, API::Workspace_const_sptr inputWS, API::Workspace_sptr outputWS)
{
// Get a pointer to the instrument contained in the workspace
boost::shared_ptr<API::Instrument> instrument = inputWS->getInstrument();
// And one to the SpectraDetectorMap
boost::shared_ptr<API::SpectraDetectorMap> specMap = inputWS->getSpectraMap();
Russell Taylor
committed
Russell Taylor
committed
// Get the unit object for each workspace
boost::shared_ptr<Unit> inputUnit = inputWS->getAxis(0)->unit();
boost::shared_ptr<Unit> outputUnit = outputWS->getAxis(0)->unit();
Russell Taylor
committed
// Get the distance between the source and the sample (assume in metres)
Geometry::ObjComponent* sample = instrument->getSample();
double l1;
Russell Taylor
committed
try
{
l1 = instrument->getSource()->getDistance(*sample);
g_log.debug() << "Source-sample distance: " << l1 << std::endl;
Russell Taylor
committed
}
catch (Exception::NotFoundError e)
{
g_log.error("Unable to calculate source-sample distance");
throw Exception::InstrumentDefinitionError("Unable to calculate source-sample distance", inputWS->getTitle());
}
Russell Taylor
committed
Geometry::V3D samplePos = sample->getPos();
const int notFailed = -99;
int failedDetectorIndex = notFailed;
// Not doing anything with the Y vector in to/fromTOF yet, so just pass empty vector
std::vector<double> emptyVec;
Russell Taylor
committed
// Loop over the histograms (detector spectra)
for (int i = 0; i < numberOfSpectra; ++i) {
Russell Taylor
committed
Russell Taylor
committed
/// @todo No implementation for any of these in the geometry yet so using properties
const int emode = getProperty("Emode");
const double efixed = getProperty("Efixed");
/// @todo Don't yet consider hold-off (delta)
const double delta = 0.0;
Russell Taylor
committed
try {
// Get the spectrum number for this histogram
Russell Taylor
committed
const int spec = inputWS->getAxis(1)->spectraNo(i);
// Now get the detector to which this relates
Russell Taylor
committed
boost::shared_ptr<Geometry::IDetector> det = specMap->getDetector(spec);
Geometry::V3D detPos = det->getPos();
// Get the sample-detector distance for this detector (in metres)
Russell Taylor
committed
double l2, twoTheta;
if ( ! det->isMonitor() )
{
l2 = detPos.distance(samplePos);
// The scattering angle for this detector (in radians).
twoTheta = instrument->detectorTwoTheta(det.get());
}
else // If this is a monitor then make l1+l2 = source-detector distance and twoTheta=0
{
l2 = detPos.distance(instrument->getSource()->getPos());
l2 = l2 - l1;
twoTheta = 0.0;
}
if (failedDetectorIndex != notFailed)
{
g_log.information() << "Unable to calculate sample-detector[" << failedDetectorIndex << "-" << i-1 << "] distance. Zeroing spectrum." << std::endl;
failedDetectorIndex = notFailed;
}
Russell Taylor
committed
// Convert the input unit to time-of-flight
Russell Taylor
committed
inputUnit->toTOF(outputWS->dataX(i),emptyVec,l1,l2,twoTheta,emode,efixed,delta);
// Convert from time-of-flight to the desired unit
Russell Taylor
committed
outputUnit->fromTOF(outputWS->dataX(i),emptyVec,l1,l2,twoTheta,emode,efixed,delta);
Russell Taylor
committed
} catch (Exception::NotFoundError e) {
// Get to here if exception thrown when calculating distance to detector
if (failedDetectorIndex == notFailed)
{
failedDetectorIndex = i;
}
outputWS->dataX(i).assign(outputWS->dataX(i).size(),0.0);
outputWS->dataY(i).assign(outputWS->dataY(i).size(),0.0);
outputWS->dataE(i).assign(outputWS->dataE(i).size(),0.0);
}
if ( i % 100 == 0)
{
progress( 0.5 + double(i)/numberOfSpectra/2 );
interruption_point();
}
} // loop over spectra
Russell Taylor
committed
if (failedDetectorIndex != notFailed)
{
g_log.information() << "Unable to calculate sample-detector[" << failedDetectorIndex << "-" << numberOfSpectra-1 << "] distance. Zeroing spectrum." << std::endl;
}
Russell Taylor
committed
}
Russell Taylor
committed
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
/// Calls Rebin as a sub-algorithm to align the bins
API::Workspace_sptr ConvertUnits::alignBins(API::Workspace_sptr workspace)
{
// Create a Rebin child algorithm
Algorithm_sptr childAlg = createSubAlgorithm("Rebin");
childAlg->setProperty<Workspace_sptr>("InputWorkspace", workspace);
childAlg->setProperty<std::vector<double> >("params",this->calculateRebinParams(workspace));
// Now execute the sub-algorithm. Catch and log any error
try
{
childAlg->execute();
}
catch (std::runtime_error& err)
{
g_log.error("Unable to successfully run Rebinning sub-algorithm");
throw;
}
if ( ! childAlg->isExecuted() )
{
g_log.error("Unable to successfully run Rebinning sub-algorithm");
throw std::runtime_error("Unable to successfully run Rebinning sub-algorithm");
}
else
{
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::Workspace_const_sptr workspace) const
{
// Need to loop round and find the full range
double XMin = DBL_MAX, XMax = DBL_MIN;
boost::shared_ptr<SpectraDetectorMap> specMap = workspace->getSpectraMap();
Axis* specAxis = workspace->getAxis(1);
const int numSpec = workspace->getNumberHistograms();
for (int i = 0; i < numSpec; ++i)
{
try {
boost::shared_ptr<Geometry::IDetector> det = specMap->getDetector(specAxis->spectraNo(i));
if ( !det->isMonitor() && !det->isDead() )
{
const std::vector<double> XData = workspace->readX(i);
if ( XData.front() < XMin ) XMin = XData.front();
if ( XData.back() > XMax ) XMax = XData.back();
}
} catch (Exception::NotFoundError) {} //Do nothing
}
const double step = ( XMax - XMin ) / workspace->blocksize();
std::vector<double> retval;
retval.push_back(XMin);
retval.push_back(step);
retval.push_back(XMax);
return retval;
}
} // namespace Algorithm
} // namespace Mantid