Skip to content
Snippets Groups Projects
MatrixWorkspaceTest.h 52.8 KiB
Newer Older
#ifndef WORKSPACETEST_H_
#define WORKSPACETEST_H_

#include "MantidAPI/NumericAxis.h"
#include "MantidAPI/Run.h"
#include "MantidAPI/SpectraAxis.h"
#include "MantidAPI/SpectrumDetectorMapping.h"
#include "MantidAPI/SpectrumInfo.h"
#include "MantidAPI/WorkspaceFactory.h"
#include "MantidGeometry/Instrument/ComponentHelper.h"
#include "MantidGeometry/Instrument/Detector.h"
#include "MantidKernel/make_cow.h"
#include "MantidKernel/TimeSeriesProperty.h"
#include "MantidKernel/VMD.h"
#include "MantidTestHelpers/FakeObjects.h"
#include "MantidTestHelpers/InstrumentCreationHelper.h"
#include "MantidTestHelpers/ComponentCreationHelper.h"
#include "MantidTestHelpers/NexusTestHelper.h"
#include "PropertyManagerHelper.h"
#include <boost/make_shared.hpp>
#include <algorithm>
#include <cmath>
#include <functional>
#include <numeric>
Peterson, Peter's avatar
Peterson, Peter committed
using std::size_t;
using namespace Mantid::Kernel;
using namespace Mantid::API;
DECLARE_WORKSPACE(WorkspaceTester)
Russell Taylor's avatar
Russell Taylor committed

/** Create a workspace with numSpectra, with
 * each spectrum having one detector, at id = workspace index.
 * @param numSpectra
 * @return
 */
boost::shared_ptr<MatrixWorkspace> makeWorkspaceWithDetectors(size_t numSpectra,
                                                              size_t numBins) {
  boost::shared_ptr<MatrixWorkspace> ws2 =
      boost::make_shared<WorkspaceTester>();
  ws2->initialize(numSpectra, numBins, numBins);
Hahn, Steven's avatar
Hahn, Steven committed
  auto inst = boost::make_shared<Instrument>("TestInstrument");
  // We get a 1:1 map by default so the detector ID should match the spectrum
  // number
  for (size_t i = 0; i < ws2->getNumberHistograms(); ++i) {
    Detector *det = new Detector("pixel", static_cast<detid_t>(i), inst.get());
    inst->add(det);
    inst->markAsDetector(det);
    ws2->getSpectrum(i).addDetectorID(static_cast<detid_t>(i));
class MatrixWorkspaceTest : public CxxTest::TestSuite {
  // This pair of boilerplate methods prevent the suite being created statically
  // This means the constructor isn't called when running other tests
  static MatrixWorkspaceTest *createSuite() {
    return new MatrixWorkspaceTest();
  static void destroySuite(MatrixWorkspaceTest *suite) { delete suite; }

Hahn, Steven's avatar
Hahn, Steven committed
  MatrixWorkspaceTest() : ws(boost::make_shared<WorkspaceTester>()) {
    ws->initialize(1, 1, 1);
  }

  void test_toString_Produces_Expected_Contents() {
    auto testWS = boost::make_shared<WorkspaceTester>();
    testWS->initialize(1, 2, 1);
    testWS->setTitle("A test run");
    testWS->getAxis(0)->setUnit("TOF");
    testWS->setYUnitLabel("Counts");

    std::string expected = "WorkspaceTester\n"
                           "Title: A test run\n"
                           "Histograms: 1\n"
                           "Bins: 1\n"
                           "Histogram\n"
                           "X axis: Time-of-flight / microsecond\n"
                           "Y axis: Counts\n"
                           "Distribution: False\n"
                           "Run start: not available\n"
                           "Run end:  not available\n";

    TS_ASSERT_EQUALS(expected, testWS->toString());
  }

  void testGetSetTitle() {
    TS_ASSERT_EQUALS(ws->getTitle(), "");
    TS_ASSERT_EQUALS(ws->getTitle(), "something");
  void testGetSetComment() {
    TS_ASSERT_EQUALS(ws->getComment(), "");
    TS_ASSERT_EQUALS(ws->getComment(), "commenting");
  void test_getIndicesFromDetectorIDs() {
    ws.initialize(10, 1, 1);
    for (size_t i = 0; i < 10; i++)
      ws.getSpectrum(i).setDetectorID(detid_t(i * 10));
    std::vector<detid_t> dets;
    dets.push_back(60);
    dets.push_back(20);
    dets.push_back(90);
    std::vector<size_t> indices = ws.getIndicesFromDetectorIDs(dets);
    TS_ASSERT_EQUALS(indices.size(), 3);
    TS_ASSERT_EQUALS(indices[0], 6);
    TS_ASSERT_EQUALS(indices[1], 2);
    TS_ASSERT_EQUALS(indices[2], 9);
  void
  test_That_A_Workspace_Gets_SpectraMap_When_Initialized_With_NVector_Elements() {
    testWS.initialize(nhist, 1, 1);
    for (size_t i = 0; i < testWS.getNumberHistograms(); i++) {
      TS_ASSERT_EQUALS(testWS.getSpectrum(i).getSpectrumNo(), specnum_t(i + 1));
      TS_ASSERT(testWS.getSpectrum(i).hasDetectorID(detid_t(i)));
  void test_updateSpectraUsing() {
    testWS.initialize(3, 1, 1);
    specnum_t specs[] = {1, 2, 2, 3};
    detid_t detids[] = {10, 99, 20, 30};
    TS_ASSERT_THROWS_NOTHING(
        testWS.updateSpectraUsing(SpectrumDetectorMapping(specs, detids, 4)));
    TS_ASSERT(testWS.getSpectrum(0).hasDetectorID(10));
    TS_ASSERT(testWS.getSpectrum(1).hasDetectorID(20));
    TS_ASSERT(testWS.getSpectrum(1).hasDetectorID(99));
    TS_ASSERT(testWS.getSpectrum(2).hasDetectorID(30));
  void testDetectorMappingCopiedWhenAWorkspaceIsCopied() {
    boost::shared_ptr<MatrixWorkspace> parent =
        boost::make_shared<WorkspaceTester>();
    parent->initialize(1, 1, 1);
    parent->getSpectrum(0).setSpectrumNo(99);
    parent->getSpectrum(0).setDetectorID(999);
    MatrixWorkspace_sptr copied = WorkspaceFactory::Instance().create(parent);
    TS_ASSERT_EQUALS(copied->getSpectrum(0).getSpectrumNo(), 99);
    TS_ASSERT(copied->getSpectrum(0).hasDetectorID(999));
  void testGetMemorySize() { TS_ASSERT_THROWS_NOTHING(ws->getMemorySize()); }
  void testHistory() { TS_ASSERT_THROWS_NOTHING(ws->history()); }
  void testAxes() { TS_ASSERT_EQUALS(ws->axes(), 2); }
  void testGetAxis() {
    TS_ASSERT_THROWS(ws->getAxis(-1), Exception::IndexError);
    TS_ASSERT_THROWS_NOTHING(ws->getAxis(0));
    TS_ASSERT(ws->getAxis(0));
    TS_ASSERT(ws->getAxis(0)->isNumeric());
    TS_ASSERT_THROWS(ws->getAxis(2), Exception::IndexError);
  void testReplaceAxis() {
    Axis *ax = new SpectraAxis(ws.get());
    TS_ASSERT_THROWS(ws->replaceAxis(2, ax), Exception::IndexError);
    TS_ASSERT_THROWS_NOTHING(ws->replaceAxis(0, ax));
    TS_ASSERT(ws->getAxis(0)->isSpectra());
  void testIsDistribution() {
    TS_ASSERT(!ws->isDistribution());
    ws->setDistribution(true);
    TS_ASSERT(ws->isDistribution());
  void testGetSetYUnit() {
    TS_ASSERT_EQUALS(ws->YUnit(), "");
    TS_ASSERT_THROWS_NOTHING(ws->setYUnit("something"));
    TS_ASSERT_EQUALS(ws->YUnit(), "something");
  void testGetSpectrum() {
    ws.initialize(4, 1, 1);
    TS_ASSERT_THROWS_NOTHING(ws.getSpectrum(0));
    TS_ASSERT_THROWS_NOTHING(ws.getSpectrum(3));
  void testGetDetector() {
    // Workspace has 3 spectra, each 1 in length
    const int numHist(3);
    boost::shared_ptr<MatrixWorkspace> workspace(
        makeWorkspaceWithDetectors(3, 1));
    for (int i = 0; i < numHist; ++i) {
      TS_ASSERT_THROWS_NOTHING(det = workspace->getDetector(i));
        TS_FAIL("No detector defined");
      }
    }

    // Now a detector group
    auto &spec = workspace->getSpectrum(0);
    spec.addDetectorID(1);
    spec.addDetectorID(2);
    TS_ASSERT_THROWS_NOTHING(det = workspace->getDetector(0));
    TS_ASSERT(det);

    // Now an empty (no detector) pixel
    auto &spec2 = workspace->getSpectrum(1);
    spec2.clearDetectorIDs();
    TS_ASSERT_THROWS_ANYTHING(det2 = workspace->getDetector(1));
    TS_ASSERT(!det2);
  }

  void testWholeSpectraMasking() {
    // Workspace has 3 spectra, each 1 in length
    const int numHist(3);
    boost::shared_ptr<MatrixWorkspace> workspace(
        makeWorkspaceWithDetectors(3, 1));
    for (int i = 0; i < numHist; ++i) {
      TS_ASSERT_EQUALS(workspace->readY(i)[0], 1.0);
      TS_ASSERT_EQUALS(workspace->readE(i)[0], 1.0);

      TS_ASSERT_THROWS_NOTHING(det = workspace->getDetector(i));
    workspace->maskWorkspaceIndex(1);
    workspace->maskWorkspaceIndex(2);
    for (int i = 0; i < numHist; ++i) {
      double expectedValue(0.0);
      bool expectedMasked(false);
      }
      TS_ASSERT_EQUALS(workspace->readY(i)[0], expectedValue);
      TS_ASSERT_EQUALS(workspace->readE(i)[0], expectedValue);

      TS_ASSERT_THROWS_NOTHING(det = workspace->getDetector(i));
        TS_ASSERT_EQUALS(det->isMasked(), expectedMasked);
  void testWholeSpectraMasking_SpectrumInfo() {
    // Workspace has 3 spectra, each 1 in length
    const int numHist(3);
    auto workspace = makeWorkspaceWithDetectors(numHist, 1);
    workspace->maskWorkspaceIndex(1);
    workspace->maskWorkspaceIndex(2);

    const auto &spectrumInfo = workspace->spectrumInfo();
    for (int i = 0; i < numHist; ++i) {
      bool expectedMasked(false);
      if (i == 0) {
        expectedMasked = false;
      } else {
        expectedMasked = true;
      }
      TS_ASSERT_EQUALS(spectrumInfo.isMasked(i), expectedMasked);
    }
  }

  void test_spectrumInfo_works_unthreaded() {
    const int numHist(3);
    auto workspace = makeWorkspaceWithDetectors(numHist, 1);
    std::atomic<bool> parallelException{false};
    for (int i = 0; i < numHist; ++i) {
      try {
        static_cast<void>(workspace->spectrumInfo());
      } catch (...) {
        parallelException = true;
      }
    }
    TS_ASSERT(!parallelException);
  }

  void test_spectrumInfo_works_threaded() {
    const int numHist(3);
    auto workspace = makeWorkspaceWithDetectors(numHist, 1);
    std::vector<const SpectrumInfo *> spectrumInfos(numHist);
    std::atomic<bool> parallelException{false};
    PARALLEL_FOR_IF(Kernel::threadSafe(*workspace))
    for (int i = 0; i < numHist; ++i) {
      try {
        spectrumInfos[i] = &(workspace->spectrumInfo());
      } catch (...) {
        parallelException = true;
      }
    }
    TS_ASSERT(!parallelException);
    for (int i = 0; i < numHist; ++i)
      TS_ASSERT_EQUALS(spectrumInfos[0], spectrumInfos[i]);
  void testFlagMasked() {
    auto ws = makeWorkspaceWithDetectors(2, 2);
    TS_ASSERT_THROWS_NOTHING(ws->flagMasked(0, 1, 0.75));
    TS_ASSERT(ws->hasMaskedBins(0));
    TS_ASSERT_EQUALS(ws->maskedBins(0).size(), 1);
    TS_ASSERT_EQUALS(ws->maskedBins(0).begin()->first, 1);
    TS_ASSERT_EQUALS(ws->maskedBins(0).begin()->second, 0.75);
    // flagMasked() shouldn't change the y-value maskBins() tested below does
    // that
    TS_ASSERT_EQUALS(ws->dataY(0)[1], 1.0);

    // Now mask a bin earlier than above and check it's sorting properly
    TS_ASSERT_THROWS_NOTHING(ws->flagMasked(1, 1))
    TS_ASSERT_EQUALS(ws->maskedBins(1).size(), 1)
    TS_ASSERT_EQUALS(ws->maskedBins(1).begin()->first, 1)
    TS_ASSERT_EQUALS(ws->maskedBins(1).begin()->second, 1.0)
    // Check the previous masking is still OK
    TS_ASSERT_EQUALS(ws->maskedBins(0).rbegin()->first, 1)
    TS_ASSERT_EQUALS(ws->maskedBins(0).rbegin()->second, 0.75)
  void testMasking() {
    auto ws2 = makeWorkspaceWithDetectors(1, 2);
    TS_ASSERT(!ws2->hasMaskedBins(0));
    // Doesn't throw on invalid spectrum number, just returns false
    TS_ASSERT(!ws2->hasMaskedBins(1));
    TS_ASSERT(!ws2->hasMaskedBins(-1));
    // Will throw if nothing masked for spectrum
    TS_ASSERT_THROWS(ws2->maskedBins(0), Mantid::Kernel::Exception::IndexError);
    // Will throw if attempting to mask invalid spectrum
    TS_ASSERT_THROWS(ws2->maskBin(-1, 1),
                     Mantid::Kernel::Exception::IndexError);
    TS_ASSERT_THROWS(ws2->maskBin(1, 1), Mantid::Kernel::Exception::IndexError);
    // ...or an invalid bin
    TS_ASSERT_THROWS(ws2->maskBin(0, -1),
                     Mantid::Kernel::Exception::IndexError);
    TS_ASSERT_THROWS(ws2->maskBin(0, 2), Mantid::Kernel::Exception::IndexError);
    // Now do a valid masking
    TS_ASSERT_THROWS_NOTHING(ws2->maskBin(0, 1, 0.5));
    TS_ASSERT(ws2->hasMaskedBins(0));
    TS_ASSERT_EQUALS(ws2->maskedBins(0).size(), 1);
    TS_ASSERT_EQUALS(ws2->maskedBins(0).begin()->first, 1);
    TS_ASSERT_EQUALS(ws2->maskedBins(0).begin()->second, 0.5);
    TS_ASSERT_EQUALS(ws2->dataY(0)[1], 0.5);
    // Now mask a bin earlier than above and check it's sorting properly
    TS_ASSERT_THROWS_NOTHING(ws2->maskBin(0, 0));
    TS_ASSERT_EQUALS(ws2->maskedBins(0).begin()->first, 0);
    TS_ASSERT_EQUALS(ws2->maskedBins(0).begin()->second, 1.0);
    TS_ASSERT_EQUALS(ws2->dataY(0)[0], 0.0);
    // Check the previous masking is still OK
    TS_ASSERT_EQUALS(ws2->maskedBins(0).rbegin()->first, 1);
    TS_ASSERT_EQUALS(ws2->maskedBins(0).rbegin()->second, 0.5);
    TS_ASSERT_EQUALS(ws2->dataY(0)[1], 0.5);
  void testMaskingNaNInf() {
    const size_t s = 4;
    const double y[s] = {NAN, INFINITY, -INFINITY, 2.};
    WorkspaceTester ws;
    ws.initialize(1, s + 1, s);

    // initialize and mask first with 0 weights
    // masking with 0 weight should be equiavalent to flagMasked
    // i.e. values should not change, even Inf and NaN
    for (size_t i = 0; i < s; ++i) {
      ws.mutableY(0)[i] = y[i];
      ws.maskBin(0, i, 0);
    }

    TS_ASSERT(std::isnan(ws.y(0)[0]));
    TS_ASSERT(std::isinf(ws.y(0)[1]));
    TS_ASSERT(std::isinf(ws.y(0)[2]));
    TS_ASSERT_EQUALS(ws.y(0)[3], 2.);

    // now mask w/o specifying weight (e.g. 1 by default)
    // in this case everything should be 0, even NaN and Inf
    for (size_t i = 0; i < s; ++i) {
      ws.maskBin(0, i);
      TS_ASSERT_EQUALS(ws.y(0)[i], 0.);
    }
  }

  void testSize() {
    wkspace.initialize(1, 4, 3);
    TS_ASSERT_EQUALS(wkspace.blocksize(), 3);
    TS_ASSERT_EQUALS(wkspace.size(), 3);
  void testBinIndexOf() {
    // Data is all 1.0s
    wkspace.dataX(0)[1] = 2.0;
    wkspace.dataX(0)[2] = 3.0;
    wkspace.dataX(0)[3] = 4.0;
    TS_ASSERT_EQUALS(wkspace.getNumberHistograms(), 1);
    TS_ASSERT_EQUALS(wkspace.binIndexOf(1.3), 0);
    TS_ASSERT_EQUALS(wkspace.binIndexOf(2.0), 0);
    TS_ASSERT_EQUALS(wkspace.binIndexOf(2.5), 1);
    TS_ASSERT_EQUALS(wkspace.binIndexOf(2.001), 1);
    TS_ASSERT_EQUALS(wkspace.binIndexOf(3.1), 2);
    TS_ASSERT_EQUALS(wkspace.binIndexOf(4.0), 2);
    TS_ASSERT_THROWS(wkspace.binIndexOf(2.5, 1), std::out_of_range);
    TS_ASSERT_THROWS(wkspace.binIndexOf(2.5, -1), std::out_of_range);
    TS_ASSERT_THROWS(wkspace.binIndexOf(5.), std::out_of_range);
    TS_ASSERT_THROWS(wkspace.binIndexOf(0.), std::out_of_range);
  void testBinIndexOfDescendingBinning() {
    WorkspaceTester wkspace;
    wkspace.initialize(1, 4, 3);

    wkspace.dataX(0)[0] = 5.3;
    wkspace.dataX(0)[1] = 4.3;
    wkspace.dataX(0)[2] = 3.3;
    wkspace.dataX(0)[3] = 2.3;

    TS_ASSERT_EQUALS(wkspace.getNumberHistograms(), 1);

    // First boundary
    TS_ASSERT_EQUALS(wkspace.binIndexOf(5.3), 0)
    // First bin
    TS_ASSERT_EQUALS(wkspace.binIndexOf(5.2), 0);
    // Bin boundary
    TS_ASSERT_EQUALS(wkspace.binIndexOf(4.3), 0);
    // Mid range
    TS_ASSERT_EQUALS(wkspace.binIndexOf(3.8), 1);
    // Still second bin
    TS_ASSERT_EQUALS(wkspace.binIndexOf(std::nextafter(3.3, 10.0)), 1);
    // Last bin
    TS_ASSERT_EQUALS(wkspace.binIndexOf(3.1), 2);
    // Last value
    TS_ASSERT_EQUALS(wkspace.binIndexOf(2.3), 2);

    // Error handling

    // Bad index value
    TS_ASSERT_THROWS(wkspace.binIndexOf(2.5, 1), std::out_of_range);
    TS_ASSERT_THROWS(wkspace.binIndexOf(2.5, -1), std::out_of_range);

    // Bad X values
    TS_ASSERT_THROWS(wkspace.binIndexOf(std::nextafter(5.3, 10.0)), std::out_of_range);
    TS_ASSERT_THROWS(wkspace.binIndexOf(5.4, 1.0), std::out_of_range);
    TS_ASSERT_THROWS(wkspace.binIndexOf(std::nextafter(2.3, 0.0)), std::out_of_range);
    TS_ASSERT_THROWS(wkspace.binIndexOf(0.), std::out_of_range);
  }

  void test_nexus_spectraMap() {
    NexusTestHelper th(true);
    th.createFile("MatrixWorkspaceTest.nxs");
    auto ws = makeWorkspaceWithDetectors(100, 50);
    for (int i = 0; i < 100; i++) {
      // Give some funny numbers, so it is not the default
      ws->getSpectrum(size_t(i)).setSpectrumNo(i * 11);
      ws->getSpectrum(size_t(i)).setDetectorID(99 - i);
      spec.push_back(i);
    }
    // Save that to the NXS file
    TS_ASSERT_THROWS_NOTHING(ws->saveSpectraMapNexus(th.file, spec););
  /** Properly, this tests a method on Instrument, not MatrixWorkspace, but they
   * are related.
   */
  void test_isDetectorMasked() {
    auto ws = makeWorkspaceWithDetectors(100, 10);
    Instrument_const_sptr inst = ws->getInstrument();
    // Make sure the instrument is parametrized so that the test is thorough
    TS_ASSERT(inst->isParametrized());
    TS_ASSERT(!inst->isDetectorMasked(1));
    TS_ASSERT(!inst->isDetectorMasked(19));
    // Mask then check that it returns as masked
    TS_ASSERT(ws->getSpectrum(19).hasDetectorID(19));
    ws->maskWorkspaceIndex(19);
    TS_ASSERT(inst->isDetectorMasked(19));
  }

  /** Check if any of a list of detectors are masked */
  void test_isDetectorMasked_onASet() {
    auto ws = makeWorkspaceWithDetectors(100, 10);
    Instrument_const_sptr inst = ws->getInstrument();
    // Make sure the instrument is parametrized so that the test is thorough
    TS_ASSERT(inst->isParametrized());

    // Mask detector IDs 8 and 9
    ws->maskWorkspaceIndex(8);
    ws->maskWorkspaceIndex(9);

    std::set<detid_t> dets;
    TSM_ASSERT("No detector IDs = not masked", !inst->isDetectorMasked(dets));
    TSM_ASSERT("Detector is not masked", !inst->isDetectorMasked(dets));
    TSM_ASSERT("Detectors are not masked", !inst->isDetectorMasked(dets));
    TSM_ASSERT("If any detector is not masked, return false",
               !inst->isDetectorMasked(dets));
    // Start again
    dets.clear();
    dets.insert(8);
    TSM_ASSERT("If all detectors are not masked, return true",
               inst->isDetectorMasked(dets));
    TSM_ASSERT("If all detectors are not masked, return true",
               inst->isDetectorMasked(dets));
    dets.insert(10);
    TSM_ASSERT("If any detector is not masked, return false",
               !inst->isDetectorMasked(dets));
  void test_hasGroupedDetectors() {
    auto ws = makeWorkspaceWithDetectors(5, 1);
    TS_ASSERT_EQUALS(ws->hasGroupedDetectors(), false);
    ws->getSpectrum(0).addDetectorID(3);
    TS_ASSERT_EQUALS(ws->hasGroupedDetectors(), true);
  void test_getSpectrumToWorkspaceIndexMap() {
    WorkspaceTester ws;
    ws.initialize(2, 1, 1);
    auto map = ws.getSpectrumToWorkspaceIndexMap();
    TS_ASSERT_EQUALS(0, map[1]);
    TS_ASSERT_EQUALS(1, map[2]);
    TS_ASSERT_EQUALS(map.size(), 2);

    // Check it throws for non-spectra axis
    ws.replaceAxis(1, new NumericAxis(1));
    TS_ASSERT_THROWS(ws.getSpectrumToWorkspaceIndexMap(), std::runtime_error);
  void test_getDetectorIDToWorkspaceIndexMap() {
    auto ws = makeWorkspaceWithDetectors(5, 1);
    detid2index_map idmap = ws->getDetectorIDToWorkspaceIndexMap(true);

    TS_ASSERT_EQUALS(idmap.size(), 5);
    for (auto it = idmap.begin(); it != idmap.end(); ++it, ++i) {
      TS_ASSERT_EQUALS(idmap.count(i), 1);
      TS_ASSERT_EQUALS(idmap[i], i);
    ws->getSpectrum(2).addDetectorID(99); // Set a second ID on one spectrum
    TS_ASSERT_THROWS(ws->getDetectorIDToWorkspaceIndexMap(true),
                     std::runtime_error);
    detid2index_map idmap2 = ws->getDetectorIDToWorkspaceIndexMap();
    TS_ASSERT_EQUALS(idmap2.size(), 6);
  void test_getDetectorIDToWorkspaceIndexVector() {
    auto ws = makeWorkspaceWithDetectors(100, 10);
    std::vector<size_t> out;
    detid_t offset = -1234;
    TS_ASSERT_THROWS_NOTHING(
        out = ws->getDetectorIDToWorkspaceIndexVector(offset));
    TS_ASSERT_EQUALS(offset, 0);
    TS_ASSERT_EQUALS(out.size(), 100);
    TS_ASSERT_EQUALS(out[0], 0);
    TS_ASSERT_EQUALS(out[1], 1);
    TS_ASSERT_EQUALS(out[99], 99);

    // Create some discontinuities and check that the default value is there
    // Have to create a whole new instrument to keep things consistent, since
    // the detector ID
    // is stored in at least 3 places
    auto inst = boost::make_shared<Instrument>("TestInstrument");
    ws->setInstrument(inst);
    // We get a 1:1 map by default so the detector ID should match the spectrum
    // number
    for (size_t i = 0; i < ws->getNumberHistograms(); ++i) {
      detid_t detid = static_cast<detid_t>(i);
      // Create a detector for each spectra
      if (i == 0)
        detid = -1;
      if (i == 99)
        detid = 110;
      Detector *det = new Detector("pixel", detid, inst.get());
      inst->add(det);
      inst->markAsDetector(det);
      ws->getSpectrum(i).addDetectorID(detid);
    ws->getSpectrum(66).clearDetectorIDs();
    TS_ASSERT_THROWS_NOTHING(
        out = ws->getDetectorIDToWorkspaceIndexVector(offset));
    TS_ASSERT_EQUALS(offset, 1);
    TS_ASSERT_EQUALS(out.size(), 112);
    TS_ASSERT_EQUALS(out[66 + offset], std::numeric_limits<size_t>::max());
    TS_ASSERT_EQUALS(out[99 + offset], 99);
    TS_ASSERT_EQUALS(out[105 + offset], std::numeric_limits<size_t>::max());
    TS_ASSERT_EQUALS(out[110 + offset], 99);
  void test_getSpectrumToWorkspaceIndexVector() {
    auto ws = makeWorkspaceWithDetectors(100, 10);
    std::vector<size_t> out;
    detid_t offset = -1234;
    TS_ASSERT_THROWS_NOTHING(out =
                                 ws->getSpectrumToWorkspaceIndexVector(offset));
    TS_ASSERT_EQUALS(offset, -1);
    TS_ASSERT_EQUALS(out.size(), 100);
    TS_ASSERT_EQUALS(out[0], 0);
    TS_ASSERT_EQUALS(out[1], 1);
    TS_ASSERT_EQUALS(out[99], 99);
  void test_getSignalAtCoord_histoData() {
    // Create a test workspace
    const auto ws = createTestWorkspace(4, 6, 5);

    // Get signal at coordinates
    std::vector<coord_t> coords = {0.5, 1.0};
    TS_ASSERT_DELTA(
        ws.getSignalAtCoord(coords.data(), Mantid::API::NoNormalization), 0.0,
        1e-5);
    TS_ASSERT_DELTA(
        ws.getSignalAtCoord(coords.data(), Mantid::API::NoNormalization), 1.0,
        1e-5);
  }

  void test_getSignalAtCoord_pointData() {
    // Create a test workspace
    const auto ws = createTestWorkspace(4, 5, 5);

    // Get signal at coordinates
    std::vector<coord_t> coords = {0.0, 1.0};
    TS_ASSERT_DELTA(
        ws.getSignalAtCoord(coords.data(), Mantid::API::NoNormalization), 0.0,
        1e-5);
    coords[0] = 1.0;
    TS_ASSERT_DELTA(
        ws.getSignalAtCoord(coords.data(), Mantid::API::NoNormalization), 1.0,
        1e-5);
  void test_getCoordAtSignal_regression() {
    Having more spectrum numbers (acutally vertical axis increments) than x bins
    in VolumeNormalisation mode
    should not cause any issues.
    */
    WorkspaceTester ws;
    const int nVertical = 4;

    const int nBins = 2;
    const int nYValues = 1;
    ws.initialize(nVertical, nBins, nYValues);
    NumericAxis *verticalAxis = new NumericAxis(nVertical);
    for (int i = 0; i < nVertical; ++i) {
      for (int j = 0; j < nBins; ++j) {
        if (j < nYValues) {
          ws.dataY(i)[j] = 1.0; // All y values are 1.
          ws.dataE(i)[j] = j;
        }
        ws.dataX(i)[j] = j; // x increments by 1
      }
      verticalAxis->setValue(i, double(i)); // Vertical axis increments by 1.
    }
    ws.replaceAxis(1, verticalAxis);
    // Signal is always 1 and volume of each box is 1. Therefore normalized
    // signal values by volume should always be 1.
    // Test at the top right.
    coord_t coord_top_right[2] = {static_cast<float>(ws.readX(0).back()),
                                  float(0)};
    signal_t value = 0;
    TS_ASSERT_THROWS_NOTHING(
        value = ws.getSignalAtCoord(coord_top_right, VolumeNormalization));
    TS_ASSERT_EQUALS(1.0, value);

    // Test at another location just to be sure.
    coord_t coord_bottom_left[2] = {
        static_cast<float>(ws.readX(nVertical - 1)[1]), float(nVertical - 1)};
    TS_ASSERT_THROWS_NOTHING(
        value = ws.getSignalAtCoord(coord_bottom_left, VolumeNormalization));
    TS_ASSERT_EQUALS(1.0, value);
  }

  void test_setMDMasking() {
    TSM_ASSERT_THROWS("Characterisation test. This is not implemented.",
                      ws.setMDMasking(NULL), std::runtime_error);
  void test_clearMDMasking() {
    TSM_ASSERT_THROWS("Characterisation test. This is not implemented.",
                      ws.clearMDMasking(), std::runtime_error);
  void test_getSpecialCoordinateSystem_default() {
    TSM_ASSERT_EQUALS("Should default to no special coordinate system.",
                      Mantid::Kernel::None, ws.getSpecialCoordinateSystem());
  void test_getFirstPulseTime_getLastPulseTime() {
    WorkspaceTester ws;
    auto proton_charge = new TimeSeriesProperty<double>("proton_charge");
    DateAndTime startTime("2013-04-21T10:40:00");
    proton_charge->addValue(startTime, 1.0E-7);
    proton_charge->addValue(startTime + 1.0, 2.0E-7);
    proton_charge->addValue(startTime + 2.0, 3.0E-7);
    proton_charge->addValue(startTime + 3.0, 4.0E-7);
    ws.mutableRun().addLogData(proton_charge);

    TS_ASSERT_EQUALS(ws.getFirstPulseTime(), startTime);
    TS_ASSERT_EQUALS(ws.getLastPulseTime(), startTime + 3.0);
  void test_getFirstPulseTime_getLastPulseTime_SNS1990bug() {
    WorkspaceTester ws;
    auto proton_charge = new TimeSeriesProperty<double>("proton_charge");
    DateAndTime startTime("1990-12-31T23:59:00");
    proton_charge->addValue(startTime, 1.0E-7);
    proton_charge->addValue(startTime + 1.0, 2.0E-7);
    ws.mutableRun().addLogData(proton_charge);

    // If fewer than 100 entries (unlikely to happen in reality), you just get
    // back the last one
    TS_ASSERT_EQUALS(ws.getFirstPulseTime(), startTime + 1.0);
    for (int i = 2; i < 62; ++i) {
      proton_charge->addValue(startTime + static_cast<double>(i), 1.0E-7);
    TS_ASSERT_EQUALS(ws.getFirstPulseTime(),
                     DateAndTime("1991-01-01T00:00:00"));
  void
  test_getFirstPulseTime_getLastPulseTime_throws_if_protoncharge_missing_or_empty() {
    WorkspaceTester ws;
    TS_ASSERT_THROWS(ws.getFirstPulseTime(), std::runtime_error);
    TS_ASSERT_THROWS(ws.getLastPulseTime(), std::runtime_error);
    ws.mutableRun().addLogData(new TimeSeriesProperty<double>("proton_charge"));
    TS_ASSERT_THROWS(ws.getFirstPulseTime(), std::runtime_error);
    TS_ASSERT_THROWS(ws.getLastPulseTime(), std::runtime_error);
  void
  test_getFirstPulseTime_getLastPulseTime_throws_if_protoncharge_wrong_type() {
    WorkspaceTester ws;
    auto proton_charge = new TimeSeriesProperty<int>("proton_charge");
    proton_charge->addValue("2013-04-21T10:19:10", 1);
    proton_charge->addValue("2013-04-21T10:19:12", 2);
    ws.mutableRun().addLogData(proton_charge);
    TS_ASSERT_THROWS(ws.getFirstPulseTime(), std::invalid_argument);
    TS_ASSERT_THROWS(ws.getLastPulseTime(), std::invalid_argument);
    ws.mutableRun().addProperty(
        new PropertyWithValue<double>("proton_charge", 99.0), true);
    TS_ASSERT_THROWS(ws.getFirstPulseTime(), std::invalid_argument);
    TS_ASSERT_THROWS(ws.getLastPulseTime(), std::invalid_argument);
  void test_getXMinMax() {
    double xmin, xmax;
    ws->getXMinMax(xmin, xmax);
    TS_ASSERT_EQUALS(xmin, 1.0);
    TS_ASSERT_EQUALS(xmax, 1.0);
    TS_ASSERT_EQUALS(ws->getXMin(), 1.0);
    TS_ASSERT_EQUALS(ws->getXMax(), 1.0);
  }

  void test_monitorWorkspace() {
    auto ws = boost::make_shared<WorkspaceTester>();
    TSM_ASSERT("There should be no monitor workspace by default",
               !ws->monitorWorkspace())
    auto ws2 = boost::make_shared<WorkspaceTester>();
    ws->setMonitorWorkspace(ws2);
    TSM_ASSERT_EQUALS("Monitor workspace not successfully set",
                      ws->monitorWorkspace(), ws2)

    ws->setMonitorWorkspace(boost::shared_ptr<MatrixWorkspace>());
    TSM_ASSERT("Monitor workspace not successfully reset",
               !ws->monitorWorkspace())
  void test_getXIndex() {
    WorkspaceTester ws;
    ws.init(1, 4, 3);
    auto &X = ws.dataX(0);
    X[0] = 1.0;
    X[1] = 2.0;
    X[2] = 3.0;
    X[3] = 4.0;

    auto ip = ws.getXIndex(0, 0.0, true);
    TS_ASSERT_EQUALS(ip.first, 0);
    TS_ASSERT_DELTA(ip.second, 0.0, 1e-15);
    ip = ws.getXIndex(0, 0.0, false);
    TS_ASSERT_EQUALS(ip.first, 4);
    TS_ASSERT_DELTA(ip.second, 0.0, 1e-15);
    ip = ws.getXIndex(0, 1.0, true);
    TS_ASSERT_EQUALS(ip.first, 0);
    TS_ASSERT_DELTA(ip.second, 0.0, 1e-15);
    ip = ws.getXIndex(0, 1.0, false);
    TS_ASSERT_EQUALS(ip.first, 4);
    TS_ASSERT_DELTA(ip.second, 0.0, 1e-15);
    ip = ws.getXIndex(0, 5.0, true);
    TS_ASSERT_EQUALS(ip.first, 4);
    TS_ASSERT_DELTA(ip.second, 0.0, 1e-15);
    ip = ws.getXIndex(0, 5.0, false);
    TS_ASSERT_EQUALS(ip.first, 3);
    TS_ASSERT_DELTA(ip.second, 0.0, 1e-15);
    ip = ws.getXIndex(0, 4.0, true);
    TS_ASSERT_EQUALS(ip.first, 4);
    TS_ASSERT_DELTA(ip.second, 0.0, 1e-15);
    ip = ws.getXIndex(0, 4.0, false);
    TS_ASSERT_EQUALS(ip.first, 3);
    TS_ASSERT_DELTA(ip.second, 0.0, 1e-15);
    ip = ws.getXIndex(0, 5.0, true, 5);
    TS_ASSERT_EQUALS(ip.first, 4);
    TS_ASSERT_DELTA(ip.second, 0.0, 1e-15);
    ip = ws.getXIndex(0, 5.0, false, 5);
    TS_ASSERT_EQUALS(ip.first, 4);
    TS_ASSERT_DELTA(ip.second, 0.0, 1e-15);
    ip = ws.getXIndex(0, 3.0, true, 5);
    TS_ASSERT_EQUALS(ip.first, 4);
    TS_ASSERT_DELTA(ip.second, 0.0, 1e-15);
    ip = ws.getXIndex(0, 3.0, false, 5);
    TS_ASSERT_EQUALS(ip.first, 4);
    TS_ASSERT_DELTA(ip.second, 0.0, 1e-15);
    ip = ws.getXIndex(0, 4.0, true, 5);
    TS_ASSERT_EQUALS(ip.first, 4);
    TS_ASSERT_DELTA(ip.second, 0.0, 1e-15);
    ip = ws.getXIndex(0, 4.0, false, 5);
    TS_ASSERT_EQUALS(ip.first, 4);
    TS_ASSERT_DELTA(ip.second, 0.0, 1e-15);
    ip = ws.getXIndex(0, 4.0, true, 4);
    TS_ASSERT_EQUALS(ip.first, 4);
    TS_ASSERT_DELTA(ip.second, 0.0, 1e-15);
    ip = ws.getXIndex(0, 4.0, false, 4);
    TS_ASSERT_EQUALS(ip.first, 4);
    TS_ASSERT_DELTA(ip.second, 0.0, 1e-15);
    ip = ws.getXIndex(0, 4.0, true, 3);
    TS_ASSERT_EQUALS(ip.first, 4);
    TS_ASSERT_DELTA(ip.second, 0.0, 1e-15);
    ip = ws.getXIndex(0, 4.0, false, 3);
    TS_ASSERT_EQUALS(ip.first, 3);
    TS_ASSERT_DELTA(ip.second, 0.0, 1e-15);
    ip = ws.getXIndex(0, 4.0, true);
    TS_ASSERT_EQUALS(ip.first, 4);
    TS_ASSERT_DELTA(ip.second, 0.0, 1e-15);
    ip = ws.getXIndex(0, 4.0, false);
    TS_ASSERT_EQUALS(ip.first, 3);
    TS_ASSERT_DELTA(ip.second, 0.0, 1e-15);
    ip = ws.getXIndex(0, 2.0, true, 3);
    TS_ASSERT_EQUALS(ip.first, 4);
    TS_ASSERT_DELTA(ip.second, 0.0, 1e-15);
    ip = ws.getXIndex(0, 2.0, false, 3);
    TS_ASSERT_EQUALS(ip.first, 3);
    TS_ASSERT_DELTA(ip.second, 0.0, 1e-15);
    ip = ws.getXIndex(0, 1.0, true, 3);
    TS_ASSERT_EQUALS(ip.first, 4);
    TS_ASSERT_DELTA(ip.second, 0.0, 1e-15);
    ip = ws.getXIndex(0, 1.0, false, 3);
    TS_ASSERT_EQUALS(ip.first, 3);
    TS_ASSERT_DELTA(ip.second, 0.0, 1e-15);
    ip = ws.getXIndex(0, 2.1, true);
    TS_ASSERT_EQUALS(ip.first, 1);
    TS_ASSERT_DELTA(ip.second, 0.1, 1e-15);
    ip = ws.getXIndex(0, 2.1, false);
    TS_ASSERT_EQUALS(ip.first, 2);
    TS_ASSERT_DELTA(ip.second, 0.9, 1e-15);
  void test_getImage_0_width() {
    ws.init(9, 2, 1);
    auto &X = ws.dataX(0);
    X[0] = 1.0;
    X[1] = 2.0;
    const size_t start = 0;
    const size_t stop = 8;
    TS_ASSERT_THROWS(ws.getImageY(start, stop, width), std::runtime_error);
    TS_ASSERT_THROWS_NOTHING(ws.getImageY(start, stop, width));
  void test_getImage_wrong_start() {
    ws.init(9, 2, 1);
    auto &X = ws.dataX(0);
    X[0] = 1.0;
    X[1] = 2.0;
    size_t stop = 8;
    TS_ASSERT_THROWS(ws.getImageY(start, stop, width), std::runtime_error);
    TS_ASSERT_THROWS(ws.getImageY(start, stop, width), std::runtime_error);
    TS_ASSERT_THROWS_NOTHING(ws.getImageY(start, stop, width));
  void test_getImage_wrong_stop() {
    ws.init(9, 2, 1);
    auto &X = ws.dataX(0);
    X[0] = 1.0;
    X[1] = 2.0;
    size_t stop = 18;
    TS_ASSERT_THROWS(ws.getImageY(start, stop, width), std::runtime_error);
    TS_ASSERT_THROWS(ws.getImageY(start, stop, width), std::runtime_error);
    TS_ASSERT_THROWS_NOTHING(ws.getImageY(start, stop, width));