LoadEventNexusTest.h 46 KB
Newer Older
1
2
3
// Mantid Repository : https://github.com/mantidproject/mantid
//
// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
4
5
//   NScD Oak Ridge National Laboratory, European Spallation Source,
//   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6
// SPDX - License - Identifier: GPL - 3.0 +
7
#pragma once
8

9
#include "MantidAPI/AlgorithmManager.h"
10
#include "MantidAPI/FrameworkManager.h"
11
#include "MantidAPI/MatrixWorkspace.h"
12
#include "MantidAPI/Run.h"
13
#include "MantidAPI/SpectrumInfo.h"
14
#include "MantidAPI/Workspace.h"
15
#include "MantidDataHandling/LoadEventNexus.h"
LamarMoore's avatar
LamarMoore committed
16
#include "MantidDataObjects/EventWorkspace.h"
17
#include "MantidGeometry/Instrument/DetectorInfo.h"
18
19
20
#include "MantidIndexing/IndexInfo.h"
#include "MantidIndexing/SpectrumIndexSet.h"
#include "MantidIndexing/SpectrumNumber.h"
LamarMoore's avatar
LamarMoore committed
21
22
#include "MantidKernel/Property.h"
#include "MantidKernel/TimeSeriesProperty.h"
23
#include "MantidNexusGeometry/Hdf5Version.h"
24
25
26
27
28
#include "MantidParallel/Collectives.h"
#include "MantidParallel/Communicator.h"
#include "MantidTestHelpers/ParallelAlgorithmCreation.h"
#include "MantidTestHelpers/ParallelRunner.h"

29
#include <cxxtest/TestSuite.h>
30

31
using namespace Mantid;
32
33
34
35
using namespace Mantid::Geometry;
using namespace Mantid::API;
using namespace Mantid::DataObjects;
using namespace Mantid::Kernel;
36
using namespace Mantid::DataHandling;
37
38
using Mantid::Types::Core::DateAndTime;
using Mantid::Types::Event::TofEvent;
39

40
void run_multiprocess_load(const std::string &file, bool precount) {
41
42
43
  Mantid::API::FrameworkManager::Instance();
  LoadEventNexus ld;
  ld.initialize();
44
  ld.setPropertyValue("Loadtype", "Multiprocess (experimental)");
45
46
47
  std::string outws_name = "multiprocess";
  ld.setPropertyValue("Filename", file);
  ld.setPropertyValue("OutputWorkspace", outws_name);
48
  ld.setPropertyValue("Precount", std::to_string(precount));
49
50
51
52
  ld.setProperty<bool>("LoadLogs", false); // Time-saver
  TS_ASSERT_THROWS_NOTHING(ld.execute());
  TS_ASSERT(ld.isExecuted())

Samuel Jones's avatar
Samuel Jones committed
53
  EventWorkspace_sptr ws = AnalysisDataService::Instance().retrieveWS<EventWorkspace>(outws_name);
54
55
56
57
  TS_ASSERT(ws);

  LoadEventNexus ldRef;
  ldRef.initialize();
58
  ldRef.setPropertyValue("Loadtype", "Default");
59
60
61
62
63
64
65
66
  outws_name = "reference";
  ldRef.setPropertyValue("Filename", file);
  ldRef.setPropertyValue("OutputWorkspace", outws_name);
  ldRef.setPropertyValue("Precount", "1");
  ldRef.setProperty<bool>("LoadLogs", false); // Time-saver
  TS_ASSERT_THROWS_NOTHING(ldRef.execute());
  TS_ASSERT(ldRef.isExecuted())

Samuel Jones's avatar
Samuel Jones committed
67
  EventWorkspace_sptr wsRef = AnalysisDataService::Instance().retrieveWS<EventWorkspace>(outws_name);
68
69
  TS_ASSERT(wsRef);

Samuel Jones's avatar
Samuel Jones committed
70
71
  TSM_ASSERT_EQUALS("Different spectrum number in reference ws.", wsRef->getNumberHistograms(),
                    ws->getNumberHistograms());
72
  if (wsRef->getNumberHistograms() != ws->getNumberHistograms())
mantid-builder's avatar
mantid-builder committed
73
    return;
74
75
76
  for (size_t i = 0; i < wsRef->getNumberHistograms(); ++i) {
    auto &eventList = ws->getSpectrum(i).getEvents();
    auto &eventListRef = wsRef->getSpectrum(i).getEvents();
Samuel Jones's avatar
Samuel Jones committed
77
    TSM_ASSERT_EQUALS("Different events number in reference spectra", eventList.size(), eventListRef.size());
78
    if (eventList.size() != eventListRef.size())
mantid-builder's avatar
mantid-builder committed
79
      return;
80
    for (size_t j = 0; j < eventListRef.size(); ++j) {
Samuel Jones's avatar
Samuel Jones committed
81
82
      TSM_ASSERT_EQUALS("Events are not equal", eventList[j].tof(), eventListRef[j].tof());
      TSM_ASSERT_EQUALS("Events are not equal", eventList[j].pulseTime(), eventListRef[j].pulseTime());
83
84
85
86
    }
  }
}

87
namespace {
Samuel Jones's avatar
Samuel Jones committed
88
std::shared_ptr<const EventWorkspace> load_reference_workspace(const std::string &filename) {
89
90
91
92
93
94
95
96
97
98
99
  // Construct default communicator *without* threading backend. In non-MPI run
  // (such as when running unit tests) this will thus just be a communicator
  // containing a single rank, independently on all ranks, which is what we want
  // for default loading bhavior.
  Parallel::Communicator comm;
  auto alg = ParallelTestHelpers::create<LoadEventNexus>(comm);
  alg->setProperty("Filename", filename);
  alg->setProperty("LoadLogs", false);
  TS_ASSERT_THROWS_NOTHING(alg->execute());
  TS_ASSERT(alg->isExecuted());
  Workspace_const_sptr out = alg->getProperty("OutputWorkspace");
100
  return std::dynamic_pointer_cast<const EventWorkspace>(out);
101
}
Samuel Jones's avatar
Samuel Jones committed
102
void run_MPI_load(const Parallel::Communicator &comm, const std::shared_ptr<std::mutex> &mutex,
103
                  const std::string &filename) {
104
105
  std::shared_ptr<const EventWorkspace> reference;
  std::shared_ptr<const EventWorkspace> eventWS;
106
107
108
109
110
111
112
113
114
115
116
117
  {
    std::lock_guard<std::mutex> lock(*mutex);
    reference = load_reference_workspace(filename);
    auto alg = ParallelTestHelpers::create<LoadEventNexus>(comm);
    alg->setProperty("Filename", filename);
    alg->setProperty("LoadLogs", false);
    TS_ASSERT_THROWS_NOTHING(alg->execute());
    TS_ASSERT(alg->isExecuted());
    Workspace_const_sptr out = alg->getProperty("OutputWorkspace");
    if (comm.size() != 1) {
      TS_ASSERT_EQUALS(out->storageMode(), Parallel::StorageMode::Distributed);
    }
118
    eventWS = std::dynamic_pointer_cast<const EventWorkspace>(out);
119
  }
120
121
122
123
124
125
126
  const size_t localSize = eventWS->getNumberHistograms();
  auto localEventCount = eventWS->getNumberEvents();
  std::vector<size_t> localSizes;
  std::vector<size_t> localEventCounts;
  Parallel::gather(comm, localSize, localSizes, 0);
  Parallel::gather(comm, localEventCount, localEventCounts, 0);
  if (comm.rank() == 0) {
Samuel Jones's avatar
Samuel Jones committed
127
    TS_ASSERT_EQUALS(std::accumulate(localSizes.begin(), localSizes.end(), static_cast<size_t>(0)),
128
                     reference->getNumberHistograms());
Samuel Jones's avatar
Samuel Jones committed
129
    TS_ASSERT_EQUALS(std::accumulate(localEventCounts.begin(), localEventCounts.end(), static_cast<size_t>(0)),
130
                     reference->getNumberEvents());
131
  }
132
133
134
135
136

  const auto &indexInfo = eventWS->indexInfo();
  size_t localCompared = 0;
  for (size_t i = 0; i < reference->getNumberHistograms(); ++i) {
    for (const auto &index :
Samuel Jones's avatar
Samuel Jones committed
137
         indexInfo.makeIndexSet({static_cast<Indexing::SpectrumNumber>(reference->getSpectrum(i).getSpectrumNo())})) {
138
139
140
141
142
143
144
145
146
      TS_ASSERT_EQUALS(eventWS->getSpectrum(index), reference->getSpectrum(i));
      ++localCompared;
    }
  }
  // Consistency check: Make sure we really compared all spectra (protects
  // against missing spectrum numbers or inconsistent mapping in IndexInfo).
  std::vector<size_t> compared;
  Parallel::gather(comm, localCompared, compared, 0);
  if (comm.rank() == 0) {
Samuel Jones's avatar
Samuel Jones committed
147
    TS_ASSERT_EQUALS(std::accumulate(compared.begin(), compared.end(), static_cast<size_t>(0)),
148
149
                     reference->getNumberHistograms());
  }
150
}
LamarMoore's avatar
LamarMoore committed
151
} // namespace
152

153
class LoadEventNexusTest : public CxxTest::TestSuite {
154
private:
Samuel Jones's avatar
Samuel Jones committed
155
  void do_test_filtering_start_and_end_filtered_loading(const bool metadataonly) {
156
157
158
159
    const std::string wsName = "test_filtering";
    const double filterStart = 1;
    const double filterEnd = 1000;

160
161
162
    LoadEventNexus ld;
    ld.initialize();
    ld.setPropertyValue("OutputWorkspace", wsName);
163
    ld.setPropertyValue("Filename", "CNCS_7860_event.nxs");
164
165
166
    ld.setProperty("FilterByTimeStart", filterStart);
    ld.setProperty("FilterByTimeStop", filterEnd);
    ld.setProperty("MetaDataOnly", metadataonly);
167

168
    TS_ASSERT(ld.execute());
169

Samuel Jones's avatar
Samuel Jones committed
170
    auto outWs = AnalysisDataService::Instance().retrieveWS<EventWorkspace>(wsName);
171

172
    Property *prop = outWs->run().getLogData("SampleTemp");
Samuel Jones's avatar
Samuel Jones committed
173
    TSM_ASSERT_EQUALS("Should have 16 elements after filtering.", 16, prop->size());
174
    if (prop->size() != 16)
175
      return;
176
    // Further tests
Samuel Jones's avatar
Samuel Jones committed
177
    TimeSeriesProperty<double> *sampleTemps = dynamic_cast<TimeSeriesProperty<double> *>(prop);
178
    auto filteredLogStartTime = sampleTemps->nthTime(0);
179
    auto filteredLogEndTime = sampleTemps->nthTime(sampleTemps->size() - 1);
Samuel Jones's avatar
Samuel Jones committed
180
181
    TS_ASSERT_EQUALS("2010-Mar-25 16:09:27.620000000", filteredLogStartTime.toSimpleString());
    TS_ASSERT_EQUALS("2010-Mar-25 16:11:51.558003540", filteredLogEndTime.toSimpleString());
182
183
  }

184
185
186
187
188
189
190
191
192
193
194
195
196
  void validate_pulse_time_sorting(EventWorkspace_sptr eventWS) {
    for (size_t i = 0; i < eventWS->getNumberHistograms(); i++) {
      auto eventList = eventWS->getSpectrum(i);
      if (eventList.getSortType() == DataObjects::PULSETIME_SORT) {
        std::vector<DateAndTime> pulsetimes;
        for (auto &event : eventList.getEvents()) {
          pulsetimes.emplace_back(event.pulseTime());
        }
        TS_ASSERT(std::is_sorted(pulsetimes.cbegin(), pulsetimes.cend()));
      }
    }
  }

197
public:
198
199
200
201
202
203
204
205
206
207
  void test_load_event_nexus_v20_ess() {
    const std::string file = "V20_ESS_example.nxs";
    LoadEventNexus alg;
    alg.setChild(true);
    alg.setRethrows(true);
    alg.initialize();
    alg.setProperty("Filename", file);
    alg.setProperty("OutputWorkspace", "dummy_for_child");
    alg.execute();
    Workspace_sptr ws = alg.getProperty("OutputWorkspace");
208
    auto eventWS = std::dynamic_pointer_cast<EventWorkspace>(ws);
209
210
211
212
213
214
215
    TS_ASSERT(eventWS);

    TS_ASSERT_EQUALS(eventWS->getNumberEvents(), 1439);
    TS_ASSERT_EQUALS(eventWS->detectorInfo().size(),
                     (150 * 150) + 2) // Two monitors
  }

216
217
218
219
220
221
222
223
224
225
226
227
228
  void test_load_event_nexus_v20_ess_log_filtered() {
    const std::string file = "V20_ESS_example.nxs";
    std::vector<std::string> allowed = {"proton_charge", "S2HGap", "S2VGap"};

    LoadEventNexus alg;
    alg.setChild(true);
    alg.setRethrows(true);
    alg.initialize();
    alg.setProperty("Filename", file);
    alg.setProperty("AllowList", allowed);
    alg.setProperty("OutputWorkspace", "dummy_for_child");
    alg.execute();
    Workspace_sptr ws = alg.getProperty("OutputWorkspace");
229
    auto eventWS = std::dynamic_pointer_cast<EventWorkspace>(ws);
230
231
232
233
234
    TS_ASSERT(eventWS);

    TS_ASSERT_EQUALS(eventWS->getNumberEvents(), 1439);
    TS_ASSERT_EQUALS(eventWS->detectorInfo().size(),
                     (150 * 150) + 2) // Two monitors
235
236

    validate_pulse_time_sorting(eventWS);
237
238
  }

239
  void test_load_event_nexus_v20_ess_integration_2018() {
240
    // Only perform this test if the version of hdf5 supports vlen strings
241
    if (NexusGeometry::Hdf5Version::checkVariableLengthStringSupport()) {
242
243
244
245
246
247
248
249
250
      const std::string file = "V20_ESSIntegration_2018-12-13_0942.nxs";
      LoadEventNexus alg;
      alg.setChild(true);
      alg.setRethrows(true);
      alg.initialize();
      alg.setProperty("Filename", file);
      alg.setProperty("OutputWorkspace", "dummy_for_child");
      alg.execute();
      Workspace_sptr ws = alg.getProperty("OutputWorkspace");
251
      auto eventWS = std::dynamic_pointer_cast<EventWorkspace>(ws);
252
253
254
255
256
257
258
      TS_ASSERT(eventWS);

      TS_ASSERT_EQUALS(eventWS->getNumberEvents(), 43277);
      TS_ASSERT_EQUALS(eventWS->detectorInfo().size(),
                       (300 * 300) + 2) // Two monitors
      TS_ASSERT_DELTA(eventWS->getTofMin(), 9.815, 1.0e-3);
      TS_ASSERT_DELTA(eventWS->getTofMax(), 130748.563, 1.0e-3);
259
260

      validate_pulse_time_sorting(eventWS);
261
    }
262
  }
263

Nick Draper's avatar
Nick Draper committed
264
  void test_NumberOfBins() {
265
    const std::string file = "SANS2D00022048.nxs";
Nick Draper's avatar
Nick Draper committed
266
    int nBins = 273;
267
268
269
270
271
272
273
274
275
    LoadEventNexus alg;
    alg.setChild(true);
    alg.setRethrows(true);
    alg.initialize();
    alg.setProperty("Filename", file);
    alg.setProperty("OutputWorkspace", "dummy_for_child");
    alg.setProperty("NumberOfBins", nBins);
    alg.execute();
    Workspace_sptr ws = alg.getProperty("OutputWorkspace");
276
    auto eventWS = std::dynamic_pointer_cast<EventWorkspace>(ws);
277
278
279
280
281
    TS_ASSERT(eventWS);

    TS_ASSERT_EQUALS(eventWS->blocksize(), nBins);
  }

282
  void test_load_event_nexus_sans2d_ess() {
283
    const std::string file = "SANS2D_ESS_example.nxs";
284
285
    LoadEventNexus alg;
    alg.setChild(true);
286
    alg.setRethrows(true);
287
288
289
    alg.initialize();
    alg.setProperty("Filename", file);
    alg.setProperty("OutputWorkspace", "dummy_for_child");
290
    alg.setProperty("NumberOfBins", 1);
291
292
    alg.execute();
    Workspace_sptr ws = alg.getProperty("OutputWorkspace");
293
    auto eventWS = std::dynamic_pointer_cast<EventWorkspace>(ws);
294
295
296
    TS_ASSERT(eventWS);

    TS_ASSERT_EQUALS(eventWS->getNumberEvents(), 14258850);
297
298
299
    TS_ASSERT_EQUALS(eventWS->counts(0)[0], 0.0);
    TS_ASSERT_EQUALS(eventWS->counts(1)[0], 2.0);
    TS_ASSERT_EQUALS(eventWS->counts(2)[0], 1.0);
Owen Arnold's avatar
Owen Arnold committed
300
    TS_ASSERT_EQUALS(eventWS->counts(122879)[0],
301
302
303
304
                     4.0); // Regession test for miss
                           // setting max detector and
                           // subsequent incorrect event
                           // count
305
306
307
308
    TS_ASSERT_EQUALS(eventWS->indexInfo().spectrumNumber(0), 1);
    TS_ASSERT_EQUALS(eventWS->indexInfo().spectrumNumber(1), 2);
    TS_ASSERT_EQUALS(eventWS->indexInfo().spectrumNumber(2), 3);
  }
309

310
#ifdef _WIN32
311
312
313
314
  bool windows = true;
#else
  bool windows = false;
#endif // _WIN32
mantid-builder's avatar
mantid-builder committed
315
  void test_multiprocess_loader_precount() {
316
    if (!windows) {
317
318
319
      run_multiprocess_load("SANS2D00022048.nxs", true);
      run_multiprocess_load("LARMOR00003368.nxs", true);
    }
mantid-builder's avatar
mantid-builder committed
320
321
322
  }

  void test_multiprocess_loader_producer_consumer() {
323
    if (!windows) {
324
325
326
      run_multiprocess_load("SANS2D00022048.nxs", false);
      run_multiprocess_load("LARMOR00003368.nxs", false);
    }
mantid-builder's avatar
mantid-builder committed
327
  }
328

329
  void test_SingleBank_PixelsOnlyInThatBank() { doTestSingleBank(true, false); }
330

331
332
333
334
335
336
337
338
339
340
341
342
  void test_load_event_nexus_ornl_eqsans() {
    // This file has a 2D entry/sample/name
    const std::string file = "EQSANS_89157.nxs.h5";
    LoadEventNexus alg;
    alg.setChild(true);
    alg.setRethrows(true);
    alg.initialize();
    alg.setProperty("Filename", file);
    alg.setProperty("MetaDataOnly", true);
    alg.setProperty("OutputWorkspace", "dummy_for_child");
    alg.execute();
    Workspace_sptr ws = alg.getProperty("OutputWorkspace");
343
    auto eventWS = std::dynamic_pointer_cast<EventWorkspace>(ws);
344
    TS_ASSERT(eventWS);
Samuel Jones's avatar
Samuel Jones committed
345
    const double duration = eventWS->mutableRun().getPropertyValueAsType<double>("duration");
346
347
348
    TS_ASSERT_DELTA(duration, 7200.012, 0.01);
  }

349
  void test_Normal_vs_Precount() {
350
351
352
353
    Mantid::API::FrameworkManager::Instance();
    LoadEventNexus ld;
    std::string outws_name = "cncs_noprecount";
    ld.initialize();
354
    ld.setRethrows(true);
355
356
    ld.setPropertyValue("Filename", "CNCS_7860_event.nxs");
    ld.setPropertyValue("OutputWorkspace", outws_name);
357
    ld.setPropertyValue("Precount", "0");
358
    ld.setProperty("NumberOfBins", 1);
359
    ld.setProperty<bool>("LoadLogs", false); // Time-saver
360
    ld.execute();
361
    TS_ASSERT(ld.isExecuted());
362

Samuel Jones's avatar
Samuel Jones committed
363
    EventWorkspace_sptr WS = AnalysisDataService::Instance().retrieveWS<EventWorkspace>(outws_name);
364
365
366
367
368
369
370
371
372
373
    // Valid WS and it is an EventWorkspace
    TS_ASSERT(WS);
    // Pixels have to be padded
    TS_ASSERT_EQUALS(WS->getNumberHistograms(), 51200);
    // Events
    TS_ASSERT_EQUALS(WS->getNumberEvents(), 112266);
    // TOF limits found. There is a pad of +-1 given around the actual TOF
    // founds.
    TS_ASSERT_DELTA((*WS->refX(0))[0], 44162.6, 0.05);
    TS_ASSERT_DELTA((*WS->refX(0))[1], 60830.2, 0.05);
374
    // Valid spectrum info
375
376
377
    TS_ASSERT_EQUALS(WS->getSpectrum(0).getSpectrumNo(), 1);
    TS_ASSERT_EQUALS(WS->getSpectrum(0).getDetectorIDs().size(), 1);
    TS_ASSERT_EQUALS(*WS->getSpectrum(0).getDetectorIDs().begin(), 0);
378

379
    // Check one event from one pixel - does it have a reasonable pulse time
Samuel Jones's avatar
Samuel Jones committed
380
    TS_ASSERT(WS->getSpectrum(1000).getEvents()[0].pulseTime() > DateAndTime(int64_t(1e9 * 365 * 10)));
381

382
    // Check filename
Samuel Jones's avatar
Samuel Jones committed
383
    TS_ASSERT_EQUALS(ld.getPropertyValue("Filename"), WS->run().getProperty("Filename")->value());
384
385
386

    // Test that asking not to load the logs did what it should
    // Make sure that we throw if we try to read a log (that shouldn't be there)
Samuel Jones's avatar
Samuel Jones committed
387
    TS_ASSERT_THROWS(WS->getLog("proton_charge"), const std::invalid_argument &);
388

389
390
391
392
    //----- Now we re-load with precounting and compare memory use ----
    LoadEventNexus ld2;
    std::string outws_name2 = "cncs_precount";
    ld2.initialize();
393
394
    ld2.setPropertyValue("Filename", "CNCS_7860_event.nxs");
    ld2.setPropertyValue("OutputWorkspace", outws_name2);
395
    ld2.setPropertyValue("Precount", "1");
396
    ld2.setProperty<bool>("LoadLogs", false); // Time-saver
397
    ld2.setProperty("NumberOfBins", 1);
398
    ld2.execute();
399
    TS_ASSERT(ld2.isExecuted());
400

Samuel Jones's avatar
Samuel Jones committed
401
    EventWorkspace_sptr WS2 = AnalysisDataService::Instance().retrieveWS<EventWorkspace>(outws_name2);
402
403
    // Valid WS and it is an EventWorkspace
    TS_ASSERT(WS2);
404

405
    TS_ASSERT_EQUALS(WS->getNumberEvents(), WS2->getNumberEvents());
406
    // Memory used should be lower (or the same at worst)
407
    TS_ASSERT_LESS_THAN_EQUALS(WS2->getMemorySize(), WS->getMemorySize());
408

409
410
    // Longer, more thorough test
    if (false) {
411
      auto load = AlgorithmManager::Instance().create("LoadEventPreNexus", 1);
412
      load->setPropertyValue("OutputWorkspace", "cncs_pre");
413
414
415
      load->setPropertyValue("EventFilename", "CNCS_7860_neutron_event.dat");
      load->setPropertyValue("PulseidFilename", "CNCS_7860_pulseid.dat");
      load->setPropertyValue("MappingFilename", "CNCS_TS_2008_08_18.dat");
416
      load->execute();
417
      TS_ASSERT(load->isExecuted());
Samuel Jones's avatar
Samuel Jones committed
418
      EventWorkspace_sptr WS2 = AnalysisDataService::Instance().retrieveWS<EventWorkspace>("cncs_pre");
419
420
421
422
423
      // Valid WS and it is an EventWorkspace
      TS_ASSERT(WS2);

      // Let's compare the proton_charge logs
      TimeSeriesProperty<double> *log =
Samuel Jones's avatar
Samuel Jones committed
424
          dynamic_cast<TimeSeriesProperty<double> *>(WS->mutableRun().getProperty("proton_charge"));
425
      std::map<DateAndTime, double> logMap = log->valueAsCorrectMap();
426
      TimeSeriesProperty<double> *log2 =
Samuel Jones's avatar
Samuel Jones committed
427
          dynamic_cast<TimeSeriesProperty<double> *>(WS2->mutableRun().getProperty("proton_charge"));
428
429
430
431
432
      std::map<DateAndTime, double> logMap2 = log2->valueAsCorrectMap();
      std::map<DateAndTime, double>::iterator it, it2;

      it = logMap.begin();
      it2 = logMap2.begin();
433
434
435
436
437
      for (; it != logMap.end();) {
        // Same times within a millisecond
        // TS_ASSERT_DELTA( it->first, it2->first,
        // DateAndTime::durationFromSeconds(1e-3));
        // Same times?
Samuel Jones's avatar
Samuel Jones committed
438
439
        TS_ASSERT_LESS_THAN(fabs(DateAndTime::secondsFromDuration(it->first - it2->first)),
                            1); // TODO: Fix the nexus file times here
440
441
        // Same proton charge?
        TS_ASSERT_DELTA(it->second, it2->second, 1e-5);
442
443
444
445
446
447
        it++;
        it2++;
      }

      int pixelID = 2000;

448
449
      std::vector<TofEvent> events1 = WS->getSpectrum(pixelID).getEvents();
      std::vector<TofEvent> events2 = WS2->getSpectrum(pixelID).getEvents();
450

451
      // std::cout << events1.size() << '\n';
452
453
454
455
456
457
458
459
460
461
      TS_ASSERT_EQUALS(events1.size(), events2.size());
      if (events1.size() == events2.size()) {
        for (size_t i = 0; i < events1.size(); i++) {
          TS_ASSERT_DELTA(events1[i].tof(), events2[i].tof(), 0.05);
          TS_ASSERT_DELTA(events1[i].pulseTime(), events2[i].pulseTime(),
                          1e9); // TODO:: Fix nexus start times
          // std::cout << (events1[i].pulseTime()-start)/1e9 << " - " <<
          // (events2[i].pulseTime()-start)/1e9 << " sec\n";
          // std::cout << "Pulse time diff " << (events1[i].pulseTime() -
          // events2[i].pulseTime())/1e9 << " sec\n";
462
463
464
465
466
        }
      }
    }
  }

467
  void test_TOF_filtered_loading() {
468
469
470
471
    const std::string wsName = "test_filtering";
    const double filterStart = 45000;
    const double filterEnd = 59000;

472
473
474
    LoadEventNexus ld;
    ld.initialize();
    ld.setPropertyValue("OutputWorkspace", wsName);
475
    ld.setPropertyValue("Filename", "CNCS_7860_event.nxs");
476
477
478
    ld.setProperty("FilterByTofMin", filterStart);
    ld.setProperty("FilterByTofMax", filterEnd);
    ld.setProperty<bool>("LoadLogs", false); // Time-saver
479

480
    TS_ASSERT(ld.execute());
481

Samuel Jones's avatar
Samuel Jones committed
482
    auto outWs = AnalysisDataService::Instance().retrieveWS<EventWorkspace>(wsName);
483

484
    auto eventList = outWs->getSpectrum(4348);
485
486
487
488
    auto events = eventList.getEvents();

    double max = events.begin()->tof();
    double min = events.begin()->tof();
489
    for (auto &event : events) {
490
491
      max = event.tof() > max ? event.tof() : max;
      min = event.tof() < min ? event.tof() : min;
492
    }
493
494
495
496
497
498
    TSM_ASSERT("The max TOF in the workspace should be equal to or less than "
               "the filtered cut-off",
               max <= filterEnd);
    TSM_ASSERT("The min TOF in the workspace should be equal to or greater "
               "than the filtered cut-off",
               min >= filterStart);
499
500
  }

501
  void test_partial_spectra_loading() {
502
503
    std::string wsName = "test_partial_spectra_loading_SpectrumList";
    std::vector<int32_t> specList;
504
505
506
507
    specList.emplace_back(13);
    specList.emplace_back(16);
    specList.emplace_back(21);
    specList.emplace_back(28);
508
509

    // A) test SpectrumList
510
511
512
    LoadEventNexus ld;
    ld.initialize();
    ld.setPropertyValue("OutputWorkspace", wsName);
513
    ld.setPropertyValue("Filename", "CNCS_7860_event.nxs");
514
    ld.setProperty("SpectrumList", specList);
515
516
    ld.setProperty<bool>("LoadLogs", false); // Time-saver

517
    TS_ASSERT(ld.execute());
518

Samuel Jones's avatar
Samuel Jones committed
519
    auto outWs = AnalysisDataService::Instance().retrieveWS<EventWorkspace>(wsName);
520

521
522
523
    TSM_ASSERT("The number of spectra in the workspace should be equal to the "
               "spectra filtered",
               outWs->getNumberHistograms() == specList.size());
524
525
526
527
528
529
    // Spectrum numbers match those that same detector would have in unfiltered
    // load, in this case detID + 1 since IDs in instrument start at 0.
    TS_ASSERT_EQUALS(outWs->getSpectrum(0).getSpectrumNo(), 14);
    TS_ASSERT_EQUALS(outWs->getSpectrum(1).getSpectrumNo(), 17);
    TS_ASSERT_EQUALS(outWs->getSpectrum(2).getSpectrumNo(), 22);
    TS_ASSERT_EQUALS(outWs->getSpectrum(3).getSpectrumNo(), 29);
530

531
532
533
534
535
536
537
    // B) test SpectrumMin and SpectrumMax
    wsName = "test_partial_spectra_loading_SpectrumMin_SpectrumMax";
    const size_t specMin = 10;
    const size_t specMax = 29;
    LoadEventNexus ldMinMax;
    ldMinMax.initialize();
    ldMinMax.setPropertyValue("OutputWorkspace", wsName);
538
    ldMinMax.setPropertyValue("Filename", "CNCS_7860_event.nxs");
539
540
541
542
    ldMinMax.setProperty<int>("SpectrumMin", specMin);
    ldMinMax.setProperty<int>("SpectrumMax", specMax);
    ldMinMax.setProperty<bool>("LoadLogs", false); // Time-saver

543
    TS_ASSERT(ldMinMax.execute());
544
545
546
547

    outWs = AnalysisDataService::Instance().retrieveWS<EventWorkspace>(wsName);

    // check number and indices of spectra
548
    const size_t numSpecs = specMax - specMin + 1;
549
    TS_ASSERT_EQUALS(outWs->getNumberHistograms(), numSpecs);
550
551
    // Spectrum numbers match those that same detector would have in unfiltered
    // load, in this case detID + 1 since IDs in instrument start at 0.
552
    for (size_t specIdx = 0; specIdx < numSpecs; specIdx++) {
Samuel Jones's avatar
Samuel Jones committed
553
      TS_ASSERT_EQUALS(outWs->getSpectrum(specIdx).getSpectrumNo(), static_cast<int>(specMin + specIdx + 1));
554
555
556
557
    }

    // C) test SpectrumList + SpectrumMin and SpectrumMax
    // This will make: 17, 20, 21, 22, 23
Samuel Jones's avatar
Samuel Jones committed
558
    wsSpecFilterAndEventMonitors = "test_partial_spectra_loading_SpectrumList_SpectrumMin_SpectrumMax";
559
560
561
    const size_t sMin = 20;
    const size_t sMax = 22;
    specList.clear();
562
    specList.emplace_back(17);
563
564
565
566

    LoadEventNexus ldLMM;
    ldLMM.initialize();
    ldLMM.setPropertyValue("OutputWorkspace", wsSpecFilterAndEventMonitors);
567
    ldLMM.setPropertyValue("Filename", "CNCS_7860_event.nxs");
568
569
570
571
572
573
    ldLMM.setProperty("SpectrumList", specList);
    ldLMM.setProperty<int>("SpectrumMin", sMin);
    ldLMM.setProperty<int>("SpectrumMax", sMax);
    ldLMM.setProperty<bool>("LoadLogs", false); // Time-saver
    // Note: this is done here to avoid additional loads
    // This will produce a workspace with suffix _monitors, that is used below
574
    // in test_CNCSMonitors
575
576
    ldLMM.setProperty<bool>("LoadMonitors", true);

577
    TS_ASSERT(ldLMM.execute());
578

Samuel Jones's avatar
Samuel Jones committed
579
    outWs = AnalysisDataService::Instance().retrieveWS<EventWorkspace>(wsSpecFilterAndEventMonitors);
580
581

    // check number and indices of spectra
Samuel Jones's avatar
Samuel Jones committed
582
    const size_t n = sMax - sMin + 1;                      // this n is the 20...22, excluding '17'
583
    TS_ASSERT_EQUALS(outWs->getNumberHistograms(), n + 1); // +1 is the '17'
584
585
586
587
    // Spectrum numbers match those that same detector would have in unfiltered
    // load, in this case detID + 1 since IDs in instrument start at 0.
    // 18 should come from SpectrumList
    TS_ASSERT_EQUALS(outWs->getSpectrum(0).getSpectrumNo(), 18);
588
    // and then sMin(20)...sMax(22)
589
    for (size_t specIdx = 0; specIdx < n; specIdx++) {
Samuel Jones's avatar
Samuel Jones committed
590
      TS_ASSERT_EQUALS(outWs->getSpectrum(specIdx + 1).getSpectrumNo(), static_cast<int>(sMin + specIdx + 1));
591
    }
592
593
  }

594
595
596
597
598
599
  void test_partial_spectra_loading_ISIS() {
    // This is to test a specific bug where if you selected any spectra and had
    // precount on you got double the number of events
    std::string wsName = "test_partial_spectra_loading_SpectrumListISIS";
    std::string wsName2 = "test_partial_spectra_loading_SpectrumListISIS2";
    std::string filename = "OFFSPEC00036416.nxs";
Nick Draper's avatar
Nick Draper committed
600
    std::vector<int32_t> specList;
601
    specList.emplace_back(45);
602

603
604
605
606
607
    LoadEventNexus ld;
    ld.initialize();
    ld.setPropertyValue("OutputWorkspace", wsName);
    ld.setPropertyValue("Filename", filename);
    ld.setProperty("SpectrumMin", 10);
Nick Draper's avatar
Nick Draper committed
608
609
    ld.setProperty("SpectrumMax", 20);
    ld.setProperty("SpectrumList", specList);
610
611
612
613
614
615
616
617
618
619
    ld.setProperty<bool>("Precount", false);
    ld.setProperty<bool>("LoadLogs", false); // Time-saver

    TS_ASSERT(ld.execute());

    LoadEventNexus ld2;
    ld2.initialize();
    ld2.setPropertyValue("OutputWorkspace", wsName2);
    ld2.setPropertyValue("Filename", filename);
    ld2.setProperty("SpectrumMin", 10);
Nick Draper's avatar
Nick Draper committed
620
    ld2.setProperty("SpectrumMax", 20);
Nick Draper's avatar
Nick Draper committed
621
    ld2.setProperty("SpectrumList", specList);
622
623
624
625
626
    ld2.setProperty<bool>("Precount", true);
    ld2.setProperty<bool>("LoadLogs", false); // Time-saver

    TS_ASSERT(ld2.execute());

Samuel Jones's avatar
Samuel Jones committed
627
628
    auto outWs = AnalysisDataService::Instance().retrieveWS<EventWorkspace>(wsName);
    auto outWs2 = AnalysisDataService::Instance().retrieveWS<EventWorkspace>(wsName2);
629

Samuel Jones's avatar
Samuel Jones committed
630
    TSM_ASSERT_EQUALS("The number of spectra in the workspace should be 12", outWs->getNumberHistograms(), 12);
631
632
633
634
635

    TSM_ASSERT_EQUALS("The number of events in the precount and not precount "
                      "workspaces do not match",
                      outWs->getNumberEvents(), outWs2->getNumberEvents());

Samuel Jones's avatar
Samuel Jones committed
636
    TSM_ASSERT("Some spectra were not found in the workspace", outWs->getSpectrum(0).getSpectrumNo() == 10);
637

Samuel Jones's avatar
Samuel Jones committed
638
639
    TSM_ASSERT("Some spectra were not found in the workspace", outWs->getSpectrum(10).getSpectrumNo() == 20);
    TSM_ASSERT("Some spectra were not found in the workspace", outWs->getSpectrum(11).getSpectrumNo() == 45);
640
641
642
643

    AnalysisDataService::Instance().remove(wsName);
    AnalysisDataService::Instance().remove(wsName2);
  }
644

Peterson, Peter's avatar
Peterson, Peter committed
645
646
647
  void test_CNCSMonitors() {
    // Re-uses the workspace loaded in test_partial_spectra_loading to save a
    // load execution
648
649
    // This is a very simple test for performance issues. There's no real event
    // data, so this just check that the algorithm creates a consistent output
Peterson, Peter's avatar
Peterson, Peter committed
650
651
    // (monitors). Real/intensive testing happens in `LoadNexusMonitors` and
    // system
652
    // tests.
Samuel Jones's avatar
Samuel Jones committed
653
    const std::string mon_outws_name = wsSpecFilterAndEventMonitors + "_monitors";
654
    auto &ads = AnalysisDataService::Instance();
655
656

    // Valid workspace and it is an event workspace
Peterson, Peter's avatar
Peterson, Peter committed
657
658
    const auto monWS = ads.retrieveWS<MatrixWorkspace>(mon_outws_name);

659
660
661
662
    TS_ASSERT(monWS);
    TS_ASSERT_EQUALS(monWS->getTitle(), "test after manual intervention");

    // Check link data --> monitor workspaces
Samuel Jones's avatar
Samuel Jones committed
663
    TS_ASSERT_EQUALS(monWS, ads.retrieveWS<MatrixWorkspace>(wsSpecFilterAndEventMonitors)->monitorWorkspace());
664
665
  }

666
  void test_Load_And_CompressEvents() {
667
668
669
670
    Mantid::API::FrameworkManager::Instance();
    LoadEventNexus ld;
    std::string outws_name = "cncs_compressed";
    ld.initialize();
671
672
    ld.setPropertyValue("Filename", "CNCS_7860_event.nxs");
    ld.setPropertyValue("OutputWorkspace", outws_name);
673
674
    ld.setPropertyValue("Precount", "0");
    ld.setPropertyValue("CompressTolerance", "0.05");
675
676
    ld.setProperty<bool>("LoadMonitors",
                         true);              // For the next test, saving a load
677
    ld.setProperty<bool>("LoadLogs", false); // Time-saver
678
    ld.execute();
679
    TS_ASSERT(ld.isExecuted());
680

681
    EventWorkspace_sptr WS;
Samuel Jones's avatar
Samuel Jones committed
682
    TS_ASSERT_THROWS_NOTHING(WS = AnalysisDataService::Instance().retrieveWS<EventWorkspace>(outws_name));
683
684
685
686
687
688
689
690
    // Valid WS and it is an EventWorkspace
    TS_ASSERT(WS);
    // Pixels have to be padded
    TS_ASSERT_EQUALS(WS->getNumberHistograms(), 51200);
    // Events
    TS_ASSERT_EQUALS(WS->getNumberEvents(),
                     111274); // There are (slightly) fewer events
    for (size_t wi = 0; wi < WS->getNumberHistograms(); wi++) {
691
      // Pixels with at least one event will have switched
692
693
      if (WS->getSpectrum(wi).getNumberEvents() > 0)
        TS_ASSERT_EQUALS(WS->getSpectrum(wi).getEventType(), WEIGHTED_NOTIME)
694
695
696
    }
  }

697
  void test_Monitors() {
698
699
    // Uses the workspace loaded in the last test to save a load execution
    std::string mon_outws_name = "cncs_compressed_monitors";
700
    auto &ads = AnalysisDataService::Instance();
701
    MatrixWorkspace_sptr WS = ads.retrieveWS<MatrixWorkspace>(mon_outws_name);
702
703
704
705
    // Valid WS and it is an MatrixWorkspace
    TS_ASSERT(WS);
    // Correct number of monitors found
    TS_ASSERT_EQUALS(WS->getNumberHistograms(), 3);
706
707
    // Check some histogram data
    // TOF
708
709
    TS_ASSERT_EQUALS((*WS->refX(0)).size(), 200002);
    TS_ASSERT_DELTA((*WS->refX(0))[1], 1.0, 1e-6);
710
    // Data
711
712
    TS_ASSERT_EQUALS(WS->dataY(0).size(), 200001);
    TS_ASSERT_DELTA(WS->dataY(0)[12], 0.0, 1e-6);
713
    // Error
714
715
    TS_ASSERT_EQUALS(WS->dataE(0).size(), 200001);
    TS_ASSERT_DELTA(WS->dataE(0)[12], 0.0, 1e-6);
716
    // Check geometry for a monitor
717
718
719
    const auto &specInfo = WS->spectrumInfo();
    TS_ASSERT(specInfo.isMonitor(2));
    TS_ASSERT_EQUALS(specInfo.detector(2).getID(), -3);
Samuel Jones's avatar
Samuel Jones committed
720
    TS_ASSERT_DELTA(specInfo.samplePosition().distance(specInfo.position(2)), 1.426, 1e-6);
721
722

    // Check monitor workspace pointer held in main workspace
Samuel Jones's avatar
Samuel Jones committed
723
    TS_ASSERT_EQUALS(WS, ads.retrieveWS<MatrixWorkspace>("cncs_compressed")->monitorWorkspace());
724
725
  }

Samuel Jones's avatar
Samuel Jones committed
726
  void doTestSingleBank(bool SingleBankPixelsOnly, bool Precount, const std::string &BankName = "bank36",
727
                        bool willFail = false) {
728
729
730
    Mantid::API::FrameworkManager::Instance();
    LoadEventNexus ld;
    std::string outws_name = "cncs";
731
    AnalysisDataService::Instance().remove(outws_name);
732
    ld.initialize();
733
734
    ld.setPropertyValue("Filename", "CNCS_7860_event.nxs");
    ld.setPropertyValue("OutputWorkspace", outws_name);
735
736
737
    ld.setPropertyValue("BankName", BankName);
    ld.setProperty<bool>("SingleBankPixelsOnly", SingleBankPixelsOnly);
    ld.setProperty<bool>("Precount", Precount);
738
    ld.setProperty<bool>("LoadLogs", false); // Time-saver
739
    ld.execute();
740

741
    EventWorkspace_sptr WS;
742
743
    if (willFail) {
      TS_ASSERT(!ld.isExecuted());
744
745
      return;
    }
746

747
    TS_ASSERT(ld.isExecuted());
Samuel Jones's avatar
Samuel Jones committed
748
    TS_ASSERT_THROWS_NOTHING(WS = AnalysisDataService::Instance().retrieveWS<EventWorkspace>(outws_name));
749
750
    // Valid WS and it is an EventWorkspace
    TS_ASSERT(WS);
751

752
    // Pixels have to be padded
Samuel Jones's avatar
Samuel Jones committed
753
    TS_ASSERT_EQUALS(WS->getNumberHistograms(), SingleBankPixelsOnly ? 1024 : 51200);
754
755
    // Events - there are fewer now.
    TS_ASSERT_EQUALS(WS->getNumberEvents(), 7274);
756
757
  }

758
  void test_SingleBank_AllPixels() { doTestSingleBank(false, false); }
759

760
  void test_SingleBank_AllPixels_Precount() { doTestSingleBank(false, true); }
761

Samuel Jones's avatar
Samuel Jones committed
762
  void test_SingleBank_PixelsOnlyInThatBank_Precount() { doTestSingleBank(true, true); }
763

Samuel Jones's avatar
Samuel Jones committed
764
  void test_SingleBank_ThatDoesntExist() { doTestSingleBank(false, false, "bankDoesNotExist", true); }
765

766
  void test_SingleBank_with_no_events() {
767
    LoadEventNexus load;
768
    TS_ASSERT_THROWS_NOTHING(load.initialize());
Samuel Jones's avatar
Samuel Jones committed
769
    TS_ASSERT_THROWS_NOTHING(load.setPropertyValue("Filename", "HYSA_12509.nxs.h5"));
770
    TS_ASSERT_THROWS_NOTHING(load.setPropertyValue("BankName", "bank10"));
771
    const std::string outws("AnEmptyWS");
772
773
    TS_ASSERT_THROWS_NOTHING(load.setPropertyValue("OutputWorkspace", outws));
    if (!load.execute()) {
774
775
776
777
778
      TS_FAIL("LoadEventNexus shouldn't fail to load an empty bank");
      return;
    }

    auto ws = AnalysisDataService::Instance().retrieveWS<EventWorkspace>(outws);
779
    TS_ASSERT_EQUALS(ws->getNumberEvents(), 0);
780
781
  }

782
  void test_instrument_inside_nexus_file() {
783
    LoadEventNexus load;
784
    TS_ASSERT_THROWS_NOTHING(load.initialize());
Samuel Jones's avatar
Samuel Jones committed
785
    TS_ASSERT_THROWS_NOTHING(load.setPropertyValue("Filename", "HYSA_12509.nxs.h5"));
786
    const std::string outws("InstInNexus");
787
788
    TS_ASSERT_THROWS_NOTHING(load.setPropertyValue("OutputWorkspace", outws));
    TS_ASSERT(load.execute());
789
790
791

    auto ws = AnalysisDataService::Instance().retrieveWS<EventWorkspace>(outws);
    auto inst = ws->getInstrument();
792
    TS_ASSERT_EQUALS(inst->getName(), "HYSPECA");
Samuel Jones's avatar
Samuel Jones committed
793
    TS_ASSERT_EQUALS(inst->getValidFromDate(), std::string("2011-Jul-20 17:02:48.437294000"));
794
    TS_ASSERT_EQUALS(inst->getNumberDetectors(), 20483);
795
    TS_ASSERT_EQUALS(inst->baseInstrument()->getMonitors().size(), 3);
796
    auto params = inst->getParameterMap();
797
798
799
800
801
802
803
804
805
806
    // Previously this was 49. Position/rotations are now stored in
    // ComponentInfo and DetectorInfo so the following four parameters are no
    // longer in the map:
    // HYSPECA/Tank;double;rotz;0
    // HYSPECA/Tank;double;rotx;0
    // HYSPECA/Tank;Quat;rot;[1,0,0,0]
    // HYSPECA/Tank;V3D;pos;[0,0,0]
    TS_ASSERT_EQUALS(params->size(), 45);
    std::cout << params->asString();

807
    TS_ASSERT_EQUALS(params->getString(inst.get(), "deltaE-mode"), "direct");
808
809
  }

810
  void test_instrument_and_default_param_loaded_when_inst_not_in_nexus_file() {
811
    LoadEventNexus load;
812
    TS_ASSERT_THROWS_NOTHING(load.initialize());
Samuel Jones's avatar
Samuel Jones committed
813
    TS_ASSERT_THROWS_NOTHING(load.setPropertyValue("Filename", "CNCS_7860_event.nxs"));
814
815
    load.setProperty<bool>("LoadLogs", false); // Time-saver
    const std::string outws("InstNotInNexus");
816
817
    TS_ASSERT_THROWS_NOTHING(load.setPropertyValue("OutputWorkspace", outws));
    TS_ASSERT(load.execute());
818
819
820

    auto ws = AnalysisDataService::Instance().retrieveWS<EventWorkspace>(outws);
    auto inst = ws->getInstrument();
821
822
823
824
825
    TS_ASSERT(!inst->getFilename().empty()); // This is how we know we didn't
                                             // get it from inside the nexus
                                             // file
    TS_ASSERT_EQUALS(inst->getName(), "CNCS");
    TS_ASSERT_EQUALS(inst->getNumberDetectors(), 51203);
826
    TS_ASSERT_EQUALS(inst->baseInstrument()->getMonitors().size(), 3);
827
828
829

    // check that CNCS_Parameters.xml has been loaded
    auto params = inst->getParameterMap();
830
    TS_ASSERT_EQUALS(params->getString(inst.get(), "deltaE-mode"), "direct");
831
832
  }

833
834
  /** Test with a particular ARCS file that has 2 preprocessors,
   * meaning different-sized pulse ID files.
835
   * DISABLED AS THE FILE ISN'T IN THE REPOSITORY
836
   */
837
  void xtest_MultiplePreprocessors() {
838
839
840
841
    Mantid::API::FrameworkManager::Instance();
    LoadEventNexus ld;
    std::string outws_name = "arcs";
    ld.initialize();
842
843
844
    try {
      ld.setPropertyValue("Filename", "ARCS_12954_event.nxs");
    } catch (...) {
845
846
847
      std::cout << "Skipping test since file does not exist.";
      return;
    }
848
    ld.setPropertyValue("OutputWorkspace", outws_name);
849
850
    ld.setPropertyValue("CompressTolerance", "-1");
    ld.execute();
851
    TS_ASSERT(ld.isExecuted());
852

853
    EventWorkspace_sptr WS;
Samuel Jones's avatar
Samuel Jones committed
854
    TS_ASSERT_THROWS_NOTHING(WS = AnalysisDataService::Instance().retrieveWS<EventWorkspace>(outws_name));
855
856
    // Valid WS and it is an EventWorkspace
    TS_ASSERT(WS);
857

858
859
860
    TS_ASSERT_EQUALS(WS->getNumberHistograms(), 117760);
    TS_ASSERT_EQUALS(WS->getNumberEvents(), 10730347);
    for (size_t wi = 0; wi < WS->getNumberHistograms(); wi++) {
861
      // Times are NON-zero for ALL pixels.
862
      if (WS->getSpectrum(wi).getNumberEvents() > 0) {
Samuel Jones's avatar
Samuel Jones committed
863
        int64_t nanosec = WS->getSpectrum(wi).getEvents()[0].pulseTime().totalNanoseconds();
864
865
        TS_ASSERT_DIFFERS(nanosec, 0)
        if (nanosec == 0) {
866
          std::cout << "Failure at WI " << wi << '\n';
867
868
          return;
        }
869
870
      }
    }