Skip to content
Snippets Groups Projects
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