Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
BinaryOperation.cpp 41.34 KiB
#include "MantidAlgorithms/BinaryOperation.h"
#include "MantidAPI/FrameworkManager.h"
#include "MantidAPI/Axis.h"
#include "MantidAPI/WorkspaceFactory.h"
#include "MantidAPI/WorkspaceProperty.h"
#include "MantidAPI/WorkspaceOpOverloads.h"
#include "MantidDataObjects/EventWorkspace.h"
#include "MantidDataObjects/EventList.h"
#include "MantidDataObjects/WorkspaceSingleValue.h"
#include "MantidGeometry/IDetector.h"
#include "MantidGeometry/Instrument/ParameterMap.h"
#include "MantidKernel/Timer.h"

#include <boost/make_shared.hpp>

using namespace Mantid::Geometry;
using namespace Mantid::API;
using namespace Mantid::Kernel;
using namespace Mantid::DataObjects;
using std::size_t;

namespace Mantid {
namespace Algorithms {
BinaryOperation::BinaryOperation()
    : API::Algorithm(), m_lhs(), m_elhs(), m_rhs(), m_erhs(), m_out(), m_eout(),
      m_AllowDifferentNumberSpectra(false), m_ClearRHSWorkspace(false),
      m_matchXSize(false), m_flipSides(false), m_keepEventWorkspace(false),
      m_useHistogramForRhsEventWorkspace(false),
      m_do2D_even_for_SingleColumn_on_rhs(false), m_indicesToMask(),
      m_progress(nullptr) {}

BinaryOperation::~BinaryOperation() {
  if (m_progress)
    delete m_progress;
}

/** Initialisation method.
 *  Defines input and output workspaces
 *
 */
void BinaryOperation::init() {
  declareProperty(
      Kernel::make_unique<WorkspaceProperty<MatrixWorkspace>>(
          inputPropName1(), "", Direction::Input),
      "The name of the input workspace on the left hand side of the operation");
  declareProperty(Kernel::make_unique<WorkspaceProperty<MatrixWorkspace>>(
                      inputPropName2(), "", Direction::Input),
                  "The name of the input workspace on the right hand side of "
                  "the operation");
  declareProperty(Kernel::make_unique<WorkspaceProperty<MatrixWorkspace>>(
                      outputPropName(), "", Direction::Output),
                  "The name to call the output workspace");
  declareProperty(
      make_unique<PropertyWithValue<bool>>("AllowDifferentNumberSpectra", false,
                                           Direction::Input),
      "Are workspaces with different number of spectra allowed? "
      "For example, the LHSWorkspace might have one spectrum per detector, "
      "but the RHSWorkspace could have its spectra averaged per bank. If true, "
      "then matching between the LHS and RHS spectra is performed (all "
      "detectors "
      "in a LHS spectrum have to be in the corresponding RHS) in order to "
      "apply the RHS spectrum to the LHS.");

  declareProperty(
      make_unique<PropertyWithValue<bool>>("ClearRHSWorkspace", false,
                                           Direction::Input),
      "For EventWorkspaces only. This will clear out event lists "
      "from the RHS workspace as the binary operation is applied. "
      "This can prevent excessive memory use, e.g. when subtracting "
      "an EventWorkspace from another: memory use will be approximately "
      "constant instead of increasing by 50%. At completion, the RHS workspace "
      "will be empty.");
}

//--------------------------------------------------------------------------------------------
/** Special handling for 1-WS and 1/WS.
 *
 * @return true if the operation was handled; exec() should then return
 */
bool BinaryOperation::handleSpecialDivideMinus() {
  // Is the LHS operand a single number?
  WorkspaceSingleValue_const_sptr lhs_singleVal =
      boost::dynamic_pointer_cast<const WorkspaceSingleValue>(m_lhs);
  WorkspaceSingleValue_const_sptr rhs_singleVal =
      boost::dynamic_pointer_cast<const WorkspaceSingleValue>(m_rhs);

  if (lhs_singleVal) {
    MatrixWorkspace_sptr out = getProperty("OutputWorkspace");
    if (this->name() == "Divide" && !bool(rhs_singleVal)) {
      // x / workspace = Power(workspace, -1) * x
      // workspace ^ -1
      IAlgorithm_sptr pow = this->createChildAlgorithm("Power", 0.0, 0.5, true);
      pow->setProperty("InputWorkspace",
                       boost::const_pointer_cast<MatrixWorkspace>(m_rhs));
      pow->setProperty("Exponent", -1.0);
      pow->setProperty("OutputWorkspace", out);
      pow->executeAsChildAlg();
      out = pow->getProperty("OutputWorkspace");

      // Multiply by x
      IAlgorithm_sptr mult =
          this->createChildAlgorithm("Multiply", 0.5, 1.0, true);
      mult->setProperty(inputPropName1(), out); //(workspace^-1)
      mult->setProperty(inputPropName2(),
                        boost::const_pointer_cast<MatrixWorkspace>(
                            m_lhs)); // (1.0) or other number
      mult->setProperty(outputPropName(), out);
      mult->executeAsChildAlg();
      out = mult->getProperty("OutputWorkspace");
      setProperty("OutputWorkspace", out);
      return true;
    } else if (this->name() == "Minus") {
      // x - workspace = x + (workspace * -1)
      MatrixWorkspace_sptr minusOne =
          WorkspaceFactory::Instance().create("WorkspaceSingleValue", 1, 1, 1);
      minusOne->dataY(0)[0] = -1.0;
      minusOne->dataE(0)[0] = 0.0;

      // workspace * -1
      IAlgorithm_sptr mult =
          this->createChildAlgorithm("Multiply", 0.0, 0.5, true);
      mult->setProperty(inputPropName1(),
                        boost::const_pointer_cast<MatrixWorkspace>(m_rhs));
      mult->setProperty(inputPropName2(), minusOne);
      mult->setProperty("OutputWorkspace", out);
      mult->executeAsChildAlg();
      out = mult->getProperty("OutputWorkspace");

      // Multiply by x
      IAlgorithm_sptr plus = this->createChildAlgorithm("Plus", 0.5, 1.0, true);
      plus->setProperty(inputPropName1(), out); //(workspace^-1)
      plus->setProperty(inputPropName2(),
                        boost::const_pointer_cast<MatrixWorkspace>(
                            m_lhs)); // (1.0) or other number
      plus->setProperty(outputPropName(), out);
      plus->executeAsChildAlg();
      out = plus->getProperty("OutputWorkspace");
      setProperty("OutputWorkspace", out);
      return true;
    }

  } // lhs_singleVal

  // Process normally
  return false;
}

//--------------------------------------------------------------------------------------------
/** Executes the algorithm. Will call execEvent() if appropriate.
 *
 *  @throw runtime_error Thrown if algorithm cannot execute
 */
void BinaryOperation::exec() {
  // get input workspace, dynamic cast not needed
  m_lhs = getProperty(inputPropName1());
  m_rhs = getProperty(inputPropName2());
  m_AllowDifferentNumberSpectra = getProperty("AllowDifferentNumberSpectra");

  // Special handling for 1-WS and 1/WS.
  if (this->handleSpecialDivideMinus())
    return;

  // Cast to EventWorkspace pointers
  m_elhs = boost::dynamic_pointer_cast<const EventWorkspace>(m_lhs);
  m_erhs = boost::dynamic_pointer_cast<const EventWorkspace>(m_rhs);

  // We can clear the RHS workspace if it is an event,
  //  and we are not doing mismatched spectra (in which case you might clear it
  //  too soon!)
  //  and lhs is not rhs.
  //  and out is not rhs.
  m_ClearRHSWorkspace = getProperty("ClearRHSWorkspace");
  if (m_ClearRHSWorkspace) {
    if (m_AllowDifferentNumberSpectra || (!m_erhs) || (m_rhs == m_lhs) ||
        (m_out == m_rhs)) {
      // std::cout << "m_ClearRHSWorkspace = false\n";
      m_ClearRHSWorkspace = false;
    }
  }

  // Get the output workspace
  m_out = getProperty(outputPropName());
  m_eout = boost::dynamic_pointer_cast<EventWorkspace>(m_out);

  // Make a check of what will be needed to setup the workspaces, based on the
  // input types.
  this->checkRequirements();

  if (m_flipSides) {
    // Flip the workspaces left and right
    std::swap(m_lhs, m_rhs);
    std::swap(m_elhs, m_erhs);
  }

  // Check that the input workspaces are compatible
  if (!checkCompatibility(m_lhs, m_rhs)) {
    std::ostringstream ostr;
    ostr << "The two workspaces are not compatible for algorithm "
         << this->name();
    g_log.error() << ostr.str() << '\n';
    throw std::invalid_argument(ostr.str());
  }

  // Is the output going to be an EventWorkspace?
  if (m_keepEventWorkspace) {
    // The output WILL be EventWorkspace (this implies lhs is EW or rhs is EW +
    // it gets flipped)
    if (!m_elhs)
      throw std::runtime_error("BinaryOperation:: the output was set to be an "
                               "EventWorkspace (m_keepEventWorkspace == true), "
                               "but the lhs is not an EventWorkspace. There "
                               "must be a mistake in the algorithm. Contact "
                               "the developers.");

    if (m_out == m_lhs) {
      // Will be modifying the EventWorkspace in-place on the lhs. Good.
      if (!m_eout)
        throw std::runtime_error(
            "BinaryOperation:: the output was set to be lhs, and to be an "
            "EventWorkspace (m_keepEventWorkspace == true), but the output is "
            "not an EventWorkspace. There must be a mistake in the algorithm. "
            "Contact the developers.");
    } else {
      // You HAVE to copy the data from lhs to to the output!
      m_out = m_lhs->clone();
      // Make sure m_eout still points to the same as m_out;
      m_eout = boost::dynamic_pointer_cast<EventWorkspace>(m_out);
    }

    // Always clear the MRUs.
    m_eout->clearMRU();
    if (m_elhs)
      m_elhs->clearMRU();
    if (m_erhs)
      m_erhs->clearMRU();

  } else {
    // ---- Output will be WS2D -------

    // We need to create a new workspace for the output if:
    //   (a) the output workspace hasn't been set to one of the input ones, or
    //   (b) it has been, but it's not the correct dimensions
    if ((m_out != m_lhs && m_out != m_rhs) ||
        (m_out == m_rhs && (m_lhs->size() > m_rhs->size()))) {
      // Make sure to delete anything that might be in the output name.
      // Removed ahead of 2.0 release to avoid problems detailed in trac #4630.
      // Hopefully temporary (see #4635).
      //          if
      //          (AnalysisDataService::Instance().doesExist(getPropertyValue(outputPropName()
      //          )))
      //            AnalysisDataService::Instance().remove(getPropertyValue(outputPropName()
      //            ));
      m_out = WorkspaceFactory::Instance().create(m_lhs);
    }
  }

  // only overridden for some operations (plus and minus at the time of writing)
  operateOnRun(m_lhs->run(), m_rhs->run(), m_out->mutableRun());

  // Initialise the progress reporting object
  m_progress = new Progress(this, 0.0, 1.0, m_lhs->getNumberHistograms());

  // There are now 4 possible scenarios, shown schematically here:
  // xxx x   xxx xxx   xxx xxx   xxx x
  // xxx   , xxx xxx , xxx     , xxx x
  // xxx   , xxx xxx   xxx       xxx x
  // So work out which one we have and call the appropriate function

  // Single value workspace on the right : if it is an EventWorkspace with 1
  // spectrum, 1 bin, it is treated as a scalar
  if ((m_rhs->size() == 1) && !m_do2D_even_for_SingleColumn_on_rhs) {
    doSingleValue();                            // m_lhs,m_rhs,m_out
  } else if (m_rhs->getNumberHistograms() == 1) // Single spectrum on rhs
  {
    doSingleSpectrum();
  }
  // Single column on rhs; if the RHS is an event workspace with one bin, it is
  // treated as a scalar.
  else if ((m_rhs->blocksize() == 1) && !m_do2D_even_for_SingleColumn_on_rhs) {
    m_indicesToMask.reserve(m_out->getNumberHistograms());
    doSingleColumn();
  } else // The two are both 2D and should be the same size (except if LHS is an
         // event workspace)
  {
    m_indicesToMask.reserve(m_out->getNumberHistograms());

    bool mismatchedSpectra =
        (m_AllowDifferentNumberSpectra &&
         (m_rhs->getNumberHistograms() != m_lhs->getNumberHistograms()));
    do2D(mismatchedSpectra);
  }

  applyMaskingToOutput(m_out);
  setOutputUnits(m_lhs, m_rhs, m_out);

  // Assign the result to the output workspace property
  setProperty(outputPropName(), m_out);
}

//--------------------------------------------------------------------------------------------
/**
 * Execute a binary operation on events. Should be overridden.
 * @param lhs :: left-hand event workspace
 * @param rhs :: right-hand event workspace
 */
void BinaryOperation::execEvent(DataObjects::EventWorkspace_const_sptr lhs,
                                DataObjects::EventWorkspace_const_sptr rhs) {
  UNUSED_ARG(lhs);
  UNUSED_ARG(rhs);
  // This should never happen
  throw Exception::NotImplementedError(
      "BinaryOperation::execEvent() is not implemented for this operation.");
}

//--------------------------------------------------------------------------------------------
/**
 * Return true if the two workspaces are compatible for this operation
 * Virtual: will be overridden as needed.
 * @param lhs :: left-hand workspace to check
 * @param rhs :: right-hand workspace to check
 * @return flag for the compatibility to the two workspaces
 */
bool BinaryOperation::checkCompatibility(
    const API::MatrixWorkspace_const_sptr lhs,
    const API::MatrixWorkspace_const_sptr rhs) const {
  Unit_const_sptr lhs_unit;
  Unit_const_sptr rhs_unit;
  if (lhs->axes() && rhs->axes()) // If one of these is a WorkspaceSingleValue
                                  // then we don't want to check units match
  {
    lhs_unit = lhs->getAxis(0)->unit();
    rhs_unit = rhs->getAxis(0)->unit();
  }

  const std::string lhs_unitID = (lhs_unit ? lhs_unit->unitID() : "");
  const std::string rhs_unitID = (rhs_unit ? rhs_unit->unitID() : "");

  // Check the workspaces have the same units and distribution flag
  if (lhs_unitID != rhs_unitID && lhs->blocksize() > 1 &&
      rhs->blocksize() > 1) {
    g_log.error("The two workspace are not compatible because they have "
                "different units on the X axis.");
    return false;
  }

  // Check the size compatibility
  std::string checkSizeCompatibilityResult = checkSizeCompatibility(lhs, rhs);
  if (!checkSizeCompatibilityResult.empty()) {
    throw std::invalid_argument(checkSizeCompatibilityResult);
  }

  return true;
}

//--------------------------------------------------------------------------------------------
/** Return true if the two workspaces can be treated as event workspaces
 * for the binary operation. If so, execEvent() will be called.
 * (e.g. Plus algorithm will concatenate event lists)
 * @param lhs :: left-hand event workspace to check
 * @param rhs :: right-hand event workspace to check
 * @return false by default; will be overridden by specific algorithms
 */
bool BinaryOperation::checkEventCompatibility(
    const API::MatrixWorkspace_const_sptr lhs,
    const API::MatrixWorkspace_const_sptr rhs) {
  UNUSED_ARG(lhs);
  UNUSED_ARG(rhs);
  return false;
}

//--------------------------------------------------------------------------------------------
/** Performs a simple check to see if the sizes of two workspaces are compatible
 * for a binary operation
 *  In order to be size compatible then the larger workspace
 *  must divide be the size of the smaller workspace leaving no remainder
 *  @param lhs :: the first workspace to compare
 *  @param rhs :: the second workspace to compare
 *  @retval "" The two workspaces are size compatible
 *  @retval "<reason why not compatible>" The two workspaces are NOT size
 * compatible
 */
std::string BinaryOperation::checkSizeCompatibility(
    const API::MatrixWorkspace_const_sptr lhs,
    const API::MatrixWorkspace_const_sptr rhs) const {
  const size_t lhsSize = lhs->size();
  const size_t rhsSize = rhs->size();
  // A SingleValueWorkspace on the right matches anything
  if (rhsSize == 1)
    return "";
  // The lhs must not be smaller than the rhs
  if (lhsSize < rhsSize)
    return "Left hand side smaller than right hand side.";

  // Did checkRequirements() tell us that the X histogram size did not matter?
  if (!m_matchXSize) {
    // If so, only the vertical # needs to match

    if (lhs->getNumberHistograms() == rhs->getNumberHistograms()) {
      return "";
    } else {
      return "Number of histograms not identical.";
    }
  }
  // Otherwise they must match both ways, or horizontally or vertically with the
  // other rhs dimension=1
  if (rhs->blocksize() == 1 &&
      lhs->getNumberHistograms() == rhs->getNumberHistograms())
    return "";
  // Past this point, we require the X arrays to match. Note this only checks
  // the first spectrum
  if (!WorkspaceHelpers::matchingBins(lhs, rhs, true)) {
    return "X arrays must match when performing this operation on a 2D "
           "workspaces.";
  }

  const size_t rhsSpec = rhs->getNumberHistograms();

  if (lhs->blocksize() == rhs->blocksize()) {
    if (rhsSpec == 1 || lhs->getNumberHistograms() == rhsSpec) {
      return "";
    } else {
      // can't be more specific as if this is reached both failed and only one
      // or both are needed
      return "Left and right sides should contain the same amount of spectra "
             "or the right side should contian only one spectra.";
    }
  } else {
    // blocksize check failed, but still check the number of spectra to see if
    // that was wrong too
    if (rhsSpec == 1 || lhs->getNumberHistograms() == rhsSpec) {
      return "Number of y values not equal on left and right sides.";
    } else {
      // can't be more specific as if this is reached both failed and only one
      // or both are needed
      return "Number of y values not equal on left and right sides and the "
             "right side contained neither only one spectra or the same amount "
             "of spectra as the left.";
    }
  }
}

//--------------------------------------------------------------------------------------------
/**
 * Checks if the spectra at the given index of either input workspace is masked.
 * If so then the output spectra has zeroed data
 * and is also masked.
 * @param lhs :: A pointer to the left-hand operand
 * @param rhs :: A pointer to the right-hand operand
 * @param index :: The workspace index to check
 * @param out :: A pointer to the output workspace
 * @returns True if further processing is not required on the spectra, false if
 * the binary operation should be performed.
 */
bool BinaryOperation::propagateSpectraMask(
    const API::MatrixWorkspace_const_sptr lhs,
    const API::MatrixWorkspace_const_sptr rhs, const int64_t index,
    API::MatrixWorkspace_sptr out) {
  bool continueOp(true);
  IDetector_const_sptr det_lhs, det_rhs;
  try {
    det_lhs = lhs->getDetector(index);
    det_rhs = rhs->getDetector(index);
  } catch (std::runtime_error &) {
  } catch (std::domain_error &) {
    // try statement will throw a domain_error when the axis is not a spectra
    // axis.
    return continueOp;
  }
  if ((det_lhs && det_lhs->isMasked()) || (det_rhs && det_rhs->isMasked())) {
    continueOp = false;
    out->maskWorkspaceIndex(index);
  }
  return continueOp;
}

/**
 * Called when the rhs operand is a single value.
 *  Loops over the lhs workspace calling the abstract binary operation function
 * with a single number as the rhs operand.
 */
void BinaryOperation::doSingleValue() {
  // Don't propate masking from the rhs here - it would be decidedly odd if the
  // single value was masked

  // Pull out the single value and its error
  const double rhsY = m_rhs->readY(0)[0];
  const double rhsE = m_rhs->readE(0)[0];

  // Now loop over the spectra of the left hand side calling the virtual
  // function
  const int64_t numHists = m_lhs->getNumberHistograms();

  if (m_eout) {
    // ---- The output is an EventWorkspace ------
    PARALLEL_FOR3(m_lhs, m_rhs, m_out)
    for (int64_t i = 0; i < numHists; ++i) {
      PARALLEL_START_INTERUPT_REGION
      m_out->setX(i, m_lhs->refX(i));
      performEventBinaryOperation(m_eout->getSpectrum(i), rhsY, rhsE);
      m_progress->report(this->name());
      PARALLEL_END_INTERUPT_REGION
    }
    PARALLEL_CHECK_INTERUPT_REGION
  } else {
    // ---- Histogram Output -----
    PARALLEL_FOR3(m_lhs, m_rhs, m_out)
    for (int64_t i = 0; i < numHists; ++i) {
      PARALLEL_START_INTERUPT_REGION
      m_out->setX(i, m_lhs->refX(i));
      // Get reference to output vectors here to break any sharing outside the
      // function call below
      // where the order of argument evaluation is not guaranteed (if it's L->R
      // there would be a data race)
      MantidVec &outY = m_out->dataY(i);
      MantidVec &outE = m_out->dataE(i);
      performBinaryOperation(m_lhs->readX(i), m_lhs->readY(i), m_lhs->readE(i),
                             rhsY, rhsE, outY, outE);
      m_progress->report(this->name());
      PARALLEL_END_INTERUPT_REGION
    }
    PARALLEL_CHECK_INTERUPT_REGION
  }
}

//--------------------------------------------------------------------------------------------
/** Called when the m_rhs operand is a 2D workspace of single values.
 *  Loops over the workspaces calling the abstract binary operation function
 * with a single number as the m_rhs operand.
 */
void BinaryOperation::doSingleColumn() {
  // Don't propate masking from the m_rhs here - it would be decidedly odd if
  // the single bin was masked

  // Now loop over the spectra of the left hand side pulling m_out the single
  // value from each m_rhs 'spectrum'
  // and then calling the virtual function
  const int64_t numHists = m_lhs->getNumberHistograms();
  if (m_eout) {
    // ---- The output is an EventWorkspace ------
    PARALLEL_FOR3(m_lhs, m_rhs, m_out)
    for (int64_t i = 0; i < numHists; ++i) {
      PARALLEL_START_INTERUPT_REGION
      const double rhsY = m_rhs->readY(i)[0];
      const double rhsE = m_rhs->readE(i)[0];

      // m_out->setX(i, m_lhs->refX(i)); //unnecessary - that was copied before.
      if (propagateSpectraMask(m_lhs, m_rhs, i, m_out)) {
        performEventBinaryOperation(m_eout->getSpectrum(i), rhsY, rhsE);
      }
      m_progress->report(this->name());
      PARALLEL_END_INTERUPT_REGION
    }
    PARALLEL_CHECK_INTERUPT_REGION
  } else {
    // ---- Histogram Output -----
    PARALLEL_FOR3(m_lhs, m_rhs, m_out)
    for (int64_t i = 0; i < numHists; ++i) {
      PARALLEL_START_INTERUPT_REGION
      const double rhsY = m_rhs->readY(i)[0];
      const double rhsE = m_rhs->readE(i)[0];

      m_out->setX(i, m_lhs->refX(i));
      if (propagateSpectraMask(m_lhs, m_rhs, i, m_out)) {
        // Get reference to output vectors here to break any sharing outside the
        // function call below
        // where the order of argument evaluation is not guaranteed (if it's
        // L->R there would be a data race)
        MantidVec &outY = m_out->dataY(i);
        MantidVec &outE = m_out->dataE(i);
        performBinaryOperation(m_lhs->readX(i), m_lhs->readY(i),
                               m_lhs->readE(i), rhsY, rhsE, outY, outE);
      }
      m_progress->report(this->name());
      PARALLEL_END_INTERUPT_REGION
    }
    PARALLEL_CHECK_INTERUPT_REGION
  }
}

//--------------------------------------------------------------------------------------------
/** Called when the m_rhs operand is a single spectrum.
 *  Loops over the lhs workspace calling the abstract binary operation function.
 */
void BinaryOperation::doSingleSpectrum() {

  // Propagate any masking first or it could mess up the numbers
  // TODO: Check if this works for event workspaces...
  propagateBinMasks(m_rhs, m_out);

  if (m_eout) {
    // ----------- The output is an EventWorkspace -------------

    if (m_erhs) {
      // -------- The rhs is ALSO an EventWorkspace --------

      // Pull out the single eventList on the right
      const EventList &rhs_spectrum = m_erhs->getSpectrum(0);

      // Now loop over the spectra of the left hand side calling the virtual
      // function
      const int64_t numHists = m_lhs->getNumberHistograms();
      PARALLEL_FOR3(m_lhs, m_rhs, m_out)
      for (int64_t i = 0; i < numHists; ++i) {
        PARALLEL_START_INTERUPT_REGION
        // m_out->setX(i,m_lhs->refX(i)); //unnecessary - that was copied
        // before.

        // Perform the operation on the event list on the output (== lhs)
        performEventBinaryOperation(m_eout->getSpectrum(i), rhs_spectrum);
        m_progress->report(this->name());
        PARALLEL_END_INTERUPT_REGION
      }
      PARALLEL_CHECK_INTERUPT_REGION
    } else {
      // -------- The rhs is a histogram ---------
      // Pull m_out the m_rhs spectrum
      const MantidVec &rhsX = m_rhs->readX(0);
      const MantidVec &rhsY = m_rhs->readY(0);
      const MantidVec &rhsE = m_rhs->readE(0);

      // Now loop over the spectra of the left hand side calling the virtual
      // function
      const int64_t numHists = m_lhs->getNumberHistograms();

      PARALLEL_FOR3(m_lhs, m_rhs, m_out)
      for (int64_t i = 0; i < numHists; ++i) {
        PARALLEL_START_INTERUPT_REGION
        // m_out->setX(i,m_lhs->refX(i)); //unnecessary - that was copied
        // before.
        // Perform the operation on the event list on the output (== lhs)
        performEventBinaryOperation(m_eout->getSpectrum(i), rhsX, rhsY, rhsE);
        m_progress->report(this->name());
        PARALLEL_END_INTERUPT_REGION
      }
      PARALLEL_CHECK_INTERUPT_REGION
    }

  } else {
    // -------- The output is a histogram ----------
    // (inputs can be EventWorkspaces, but their histogram representation
    //  will be used instead)

    // Pull m_out the m_rhs spectrum
    const MantidVec &rhsY = m_rhs->readY(0);
    const MantidVec &rhsE = m_rhs->readE(0);

    // Now loop over the spectra of the left hand side calling the virtual
    // function
    const int64_t numHists = m_lhs->getNumberHistograms();

    PARALLEL_FOR3(m_lhs, m_rhs, m_out)
    for (int64_t i = 0; i < numHists; ++i) {
      PARALLEL_START_INTERUPT_REGION
      m_out->setX(i, m_lhs->refX(i));
      // Get reference to output vectors here to break any sharing outside the
      // function call below
      // where the order of argument evaluation is not guaranteed (if it's L->R
      // there would be a data race)
      MantidVec &outY = m_out->dataY(i);
      MantidVec &outE = m_out->dataE(i);
      performBinaryOperation(m_lhs->readX(i), m_lhs->readY(i), m_lhs->readE(i),
                             rhsY, rhsE, outY, outE);
      m_progress->report(this->name());
      PARALLEL_END_INTERUPT_REGION
    }
    PARALLEL_CHECK_INTERUPT_REGION
  }
}

//--------------------------------------------------------------------------------------------
/** Called when the two workspaces are the same size.
 *  Loops over the workspaces extracting the appropriate spectra and calling the
 *abstract binary operation function.
 *
 *  @param mismatchedSpectra :: allow the # of spectra to not be the same. Will
 *use the
 *      detector IDs to find the corresponding spectrum on RHS
 */
void BinaryOperation::do2D(bool mismatchedSpectra) {
  BinaryOperationTable_sptr table;
  if (mismatchedSpectra) {
    table = BinaryOperation::buildBinaryOperationTable(m_lhs, m_rhs);
  }

  // Propagate any masking first or it could mess up the numbers
  // TODO: Check if this works for event workspaces...
  propagateBinMasks(m_rhs, m_out);

  if (m_eout) {
    // ----------- The output is an EventWorkspace -------------

    if (m_erhs && !m_useHistogramForRhsEventWorkspace) {
      // ------------ The rhs is ALSO an EventWorkspace ---------------
      // Now loop over the spectra of each one calling the virtual function
      const int64_t numHists = m_lhs->getNumberHistograms();
      PARALLEL_FOR3(m_lhs, m_rhs, m_out)
      for (int64_t i = 0; i < numHists; ++i) {
        PARALLEL_START_INTERUPT_REGION
        m_progress->report(this->name());
        int64_t rhs_wi = i;
        if (mismatchedSpectra && table) {
          rhs_wi = (*table)[i];
          if (rhs_wi < 0)
            continue;
        } else {
          // Check for masking except when mismatched sizes
          if (!propagateSpectraMask(m_lhs, m_rhs, i, m_out))
            continue;
        }
        // Reach here? Do the division
        // Perform the operation on the event list on the output (== lhs)
        performEventBinaryOperation(m_eout->getSpectrum(i),
                                    m_erhs->getSpectrum(rhs_wi));

        // Free up memory on the RHS if that is possible
        if (m_ClearRHSWorkspace)
          const_cast<EventList &>(m_erhs->getSpectrum(rhs_wi)).clear();
        PARALLEL_END_INTERUPT_REGION
      }
      PARALLEL_CHECK_INTERUPT_REGION
    } else {
      // -------- The rhs is a histogram, or we want to use the histogram
      // representation of it ---------

      // Now loop over the spectra of each one calling the virtual function
      const int64_t numHists = m_lhs->getNumberHistograms();

      PARALLEL_FOR3(m_lhs, m_rhs, m_out)
      for (int64_t i = 0; i < numHists; ++i) {
        PARALLEL_START_INTERUPT_REGION
        m_progress->report(this->name());
        int64_t rhs_wi = i;
        if (mismatchedSpectra && table) {
          rhs_wi = (*table)[i];
          if (rhs_wi < 0)
            continue;
        } else {
          // Check for masking except when mismatched sizes
          if (!propagateSpectraMask(m_lhs, m_rhs, i, m_out))
            continue;
        }

        // Reach here? Do the division
        performEventBinaryOperation(m_eout->getSpectrum(i),
                                    m_rhs->readX(rhs_wi), m_rhs->readY(rhs_wi),
                                    m_rhs->readE(rhs_wi));

        // Free up memory on the RHS if that is possible
        if (m_ClearRHSWorkspace)
          const_cast<EventList &>(m_erhs->getSpectrum(rhs_wi)).clear();

        PARALLEL_END_INTERUPT_REGION
      }
      PARALLEL_CHECK_INTERUPT_REGION
    }

  } else {
    // -------- The output is a histogram ----------
    // (inputs can be EventWorkspaces, but their histogram representation
    //  will be used instead)

    // Now loop over the spectra of each one calling the virtual function
    const int64_t numHists = m_lhs->getNumberHistograms();

    PARALLEL_FOR3(m_lhs, m_rhs, m_out)
    for (int64_t i = 0; i < numHists; ++i) {
      PARALLEL_START_INTERUPT_REGION
      m_progress->report(this->name());
      m_out->setX(i, m_lhs->refX(i));
      int64_t rhs_wi = i;
      if (mismatchedSpectra && table) {
        rhs_wi = (*table)[i];
        if (rhs_wi < 0)
          continue;
      } else {
        // Check for masking except when mismatched sizes
        if (!propagateSpectraMask(m_lhs, m_rhs, i, m_out))
          continue;
      }
      // Reach here? Do the division
      // Get reference to output vectors here to break any sharing outside the
      // function call below
      // where the order of argument evaluation is not guaranteed (if it's L->R
      // there would be a data race)
      MantidVec &outY = m_out->dataY(i);
      MantidVec &outE = m_out->dataE(i);
      performBinaryOperation(m_lhs->readX(i), m_lhs->readY(i), m_lhs->readE(i),
                             m_rhs->readY(rhs_wi), m_rhs->readE(rhs_wi), outY,
                             outE);

      // Free up memory on the RHS if that is possible
      if (m_ClearRHSWorkspace)
        const_cast<EventList &>(m_erhs->getSpectrum(rhs_wi)).clear();

      PARALLEL_END_INTERUPT_REGION
    }
    PARALLEL_CHECK_INTERUPT_REGION
  }
  // Make sure we don't use outdated MRU
  if (m_ClearRHSWorkspace)
    m_erhs->clearMRU();
}

/** Copies any bin masking from the smaller/rhs input workspace to the output.
 *  Masks on the other input workspace are copied automatically by the workspace
 * factory.
 *  @param rhs :: The workspace which is the right hand operand
 *  @param out :: The result workspace
 */
void BinaryOperation::propagateBinMasks(
    const API::MatrixWorkspace_const_sptr rhs, API::MatrixWorkspace_sptr out) {
  const int64_t outHists = out->getNumberHistograms();
  const int64_t rhsHists = rhs->getNumberHistograms();
  for (int64_t i = 0; i < outHists; ++i) {
    // Copy over masks from the rhs, if any exist.
    // If rhs is single spectrum, copy masks from that to all spectra in the
    // output.
    if (rhs->hasMaskedBins((rhsHists == 1) ? 0 : i)) {
      const MatrixWorkspace::MaskList &masks =
          rhs->maskedBins((rhsHists == 1) ? 0 : i);
      MatrixWorkspace::MaskList::const_iterator it;
      for (it = masks.begin(); it != masks.end(); ++it) {
        out->flagMasked(i, it->first, it->second);
      }
    }
  }
}

//---------------------------------------------------------------------------------------------
/**
 * Apply the requested masking to the output workspace
 * @param out :: The workspace to mask
 */
void BinaryOperation::applyMaskingToOutput(API::MatrixWorkspace_sptr out) {
  int64_t nindices = static_cast<int64_t>(m_indicesToMask.size());
  ParameterMap &pmap = out->instrumentParameters();
  PARALLEL_FOR1(out)
  for (int64_t i = 0; i < nindices; ++i) {
    if (!m_parallelException && !m_cancel) {
      try {
        IDetector_const_sptr det_out = out->getDetector(m_indicesToMask[i]);
        PARALLEL_CRITICAL(BinaryOperation_masking) {
          pmap.addBool(det_out.get(), "masked", true);
        }
      } /* End of try block in PARALLEL_START_INTERUPT_REGION */
      catch (Kernel::Exception::NotFoundError) { // detector not found, do
                                                 // nothing, go further
      } catch (std::runtime_error &ex) {
        if (!m_parallelException) {
          m_parallelException = true;
          g_log.error() << this->name() << ": " << ex.what() << "\n";
        }
      } catch (...) {
        m_parallelException = true;
      }

    } // End of if block in PARALLEL_START_INTERUPT_REGION
  }
  PARALLEL_CHECK_INTERUPT_REGION
}

// ------- Default implementations of Event binary operations --------

/**
 * Carries out the binary operation IN-PLACE on a single EventList,
 * with another EventList as the right-hand operand.
 * The event lists simply get appended.
 *
 *  @param lhs :: Reference to the EventList that will be modified in place.
 *  @param rhs :: Const reference to the EventList on the right hand side.
 */
void BinaryOperation::performEventBinaryOperation(
    DataObjects::EventList &lhs, const DataObjects::EventList &rhs) {
  UNUSED_ARG(lhs);
  UNUSED_ARG(rhs);
  throw Exception::NotImplementedError(
      "BinaryOperation::performEventBinaryOperation() not implemented.");
}

/**
 * Carries out the binary operation IN-PLACE on a single EventList,
 * with another (histogrammed) spectrum as the right-hand operand.
 *
 *  @param lhs :: Reference to the EventList that will be modified in place.
 *  @param rhsX :: The vector of rhs X bin boundaries
 *  @param rhsY :: The vector of rhs data values
 *  @param rhsE :: The vector of rhs error values
 */
void BinaryOperation::performEventBinaryOperation(DataObjects::EventList &lhs,
                                                  const MantidVec &rhsX,
                                                  const MantidVec &rhsY,
                                                  const MantidVec &rhsE) {
  UNUSED_ARG(lhs);
  UNUSED_ARG(rhsX);
  UNUSED_ARG(rhsY);
  UNUSED_ARG(rhsE);
  throw Exception::NotImplementedError(
      "BinaryOperation::performEventBinaryOperation() not implemented.");
}

/**
 * Carries out the binary operation IN-PLACE on a single EventList,
 * with a single (double) value as the right-hand operand
 *
 *  @param lhs :: Reference to the EventList that will be modified in place.
 *  @param rhsY :: The rhs data value
 *  @param rhsE :: The rhs error value
 */
void BinaryOperation::performEventBinaryOperation(DataObjects::EventList &lhs,
                                                  const double &rhsY,
                                                  const double &rhsE) {
  UNUSED_ARG(lhs);
  UNUSED_ARG(rhsY);
  UNUSED_ARG(rhsE);
  throw Exception::NotImplementedError(
      "BinaryOperation::performEventBinaryOperation() not implemented.");
}

//---------------------------------------------------------------------------------------------
/**
 * Get the type of operand from a workspace
 * @param ws :: workspace to check
 * @return OperandType describing what type of workspace it will be operated as.
 */
OperandType
BinaryOperation::getOperandType(const API::MatrixWorkspace_const_sptr ws) {
  // An event workspace?
  EventWorkspace_const_sptr ews =
      boost::dynamic_pointer_cast<const EventWorkspace>(ws);
  if (ews)
    return eEventList;

  // If the workspace has no axes, then it is a WorkspaceSingleValue
  if (!ws->axes())
    return eNumber;

  // TODO: Check if it is a single-colum one, then
  //  return Number;

  // Otherwise, we take it as a histogram (workspace 2D)
  return eHistogram;
}

//---------------------------------------------------------------------------------------------
/** Check what operation will be needed in order to apply the operation
 * to these two types of workspaces. This function must be overridden
 * and checked against all 9 possible combinations.
 *
 * Must set: m_matchXSize, m_flipSides, m_keepEventWorkspace
 */
void BinaryOperation::checkRequirements() {

  // In general, the X sizes have to match.
  //  (only for some EventWorkspace cases does it NOT matter...)
  m_matchXSize = true;

  // Operations are not always commutative! Don't flip sides.
  m_flipSides = false;

  // And in general, EventWorkspaces get turned to Workspace2D
  m_keepEventWorkspace = false;

  // This will be set to true for Divide/Multiply
  m_useHistogramForRhsEventWorkspace = false;
}

//---------------------------------------------------------------------------------------------
/** Build up an BinaryOperationTable for performing a binary operation
 * e.g. lhs = (lhs + rhs)
 * where the spectra in rhs are to go into lhs.
 * This function looks to match the detector IDs in rhs to those in the lhs.
 *
 * @param lhs :: matrix workspace in which the operation is being done.
 * @param rhs :: matrix workspace on the right hand side of the operand
 * @return map from detector ID to workspace index for the RHS workspace.
 *        NULL if there is not a 1:1 mapping from detector ID to workspace index
 *(e.g more than one detector per pixel).
 */
BinaryOperation::BinaryOperationTable_sptr
BinaryOperation::buildBinaryOperationTable(
    const MatrixWorkspace_const_sptr &lhs,
    const MatrixWorkspace_const_sptr &rhs) {
  // An addition table is a list of pairs:
  //  First int = workspace index in the EW being added
  //  Second int = workspace index to which it will be added in the OUTPUT EW.
  //  -1 if it should add a new entry at the end.
  auto table = boost::make_shared<BinaryOperationTable>();

  int rhs_nhist = static_cast<int>(rhs->getNumberHistograms());
  int lhs_nhist = static_cast<int>(lhs->getNumberHistograms());

  // Initialize the table; filled with -1 meaning no match
  table->resize(lhs_nhist, -1);

  const detid2index_map rhs_det_to_wi = rhs->getDetectorIDToWorkspaceIndexMap();

  PARALLEL_FOR_NO_WSP_CHECK()
  for (int lhsWI = 0; lhsWI < lhs_nhist; lhsWI++) {
    bool done = false;

    // List of detectors on lhs side
    const auto &lhsDets = lhs->getSpectrum(lhsWI).getDetectorIDs();

    // ----------------- Matching Workspace Indices and Detector IDs
    // --------------------------------------
    // First off, try to match the workspace indices. Most times, this will be
    // ok right away.
    int64_t rhsWI = lhsWI;
    if (rhsWI < rhs_nhist) // don't go out of bounds
    {
      // Get the detector IDs at that workspace index.
      const auto &rhsDets = rhs->getSpectrum(rhsWI).getDetectorIDs();

      // Checks that lhsDets is a subset of rhsDets
      if (std::includes(rhsDets.begin(), rhsDets.end(), lhsDets.begin(),
                        lhsDets.end())) {
        // We found the workspace index right away. No need to keep looking
        (*table)[lhsWI] = rhsWI;
        done = true;
      }
    }

    // ----------------- Scrambled Detector IDs with one Detector per Spectrum
    // --------------------------------------
    if (!done && (lhsDets.size() == 1)) {
      // Didn't find it. Try to use the RHS map.

      // First, we have to get the (single) detector ID of the LHS
      auto lhsDets_it = lhsDets.cbegin();
      detid_t lhs_detector_ID = *lhsDets_it;

      // Now we use the RHS map to find it. This only works if both the lhs and
      // rhs have 1 detector per pixel
      auto map_it = rhs_det_to_wi.find(lhs_detector_ID);
      if (map_it != rhs_det_to_wi.end()) {
        rhsWI = map_it->second; // This is the workspace index in the RHS that
                                // matched lhs_detector_ID
      } else {
        // Did not find it!
        rhsWI = -1; // Marker to mean its not in the LHS.

        //            std::ostringstream mess;
        //            mess << "BinaryOperation: cannot find a RHS spectrum that
        //            contains the detectors in LHS workspace index " << lhsWI
        //            << "\n";
        //            throw std::runtime_error(mess.str());
      }
      (*table)[lhsWI] = rhsWI;
      done = true; // Great, we did it.
    }

    // ----------------- LHS detectors are subset of RHS, which are Grouped
    // --------------------------------------
    if (!done) {

      // Didn't find it? Now we need to iterate through the output workspace to
      //  match the detector ID.
      // NOTE: This can be SUPER SLOW!
      for (rhsWI = 0; rhsWI < static_cast<int64_t>(rhs_nhist); rhsWI++) {
        const auto &rhsDets = rhs->getSpectrum(rhsWI).getDetectorIDs();

        // Checks that lhsDets is a subset of rhsDets
        if (std::includes(rhsDets.begin(), rhsDets.end(), lhsDets.begin(),
                          lhsDets.end())) {
          // This one is right. Now we can stop looking.
          (*table)[lhsWI] = rhsWI;
          done = true;
          continue;
        }
      }
    }

    // ------- Still nothing ! -----------
    if (!done) {
      (*table)[lhsWI] = -1;

      //          std::ostringstream mess;
      //          mess << "BinaryOperation: cannot find a RHS spectrum that
      //          contains the detectors in LHS workspace index " << lhsWI <<
      //          "\n";
      //          throw std::runtime_error(mess.str());
    }
  }

  return table;
}

} // namespace Algorithms
} // namespace Mantid