Mantid
Loading...
Searching...
No Matches
AlignAndFocusPowderSlim.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2024 ISIS Rutherford Appleton Laboratory UKRI,
4// NScD Oak Ridge National Laboratory, European Spallation Source,
5// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6// SPDX - License - Identifier: GPL - 3.0 +
7
10#include "MantidAPI/Axis.h"
13#include "MantidAPI/Run.h"
14#include "MantidAPI/Sample.h"
34#include "MantidKernel/Timer.h"
35#include "MantidKernel/Unit.h"
36#include "MantidKernel/V3D.h"
38#include "MantidNexus/H5Util.h"
39
40#include <H5Cpp.h>
41#include <numbers>
42#include <regex>
43#include <vector>
44
61
62namespace { // anonymous namespace
63
64const std::string LOG_CHARGE_NAME("proton_charge");
65
66const std::vector<std::string> binningModeNames{"Logarithmic", "Linear"};
67enum class BinningMode { LOGARITHMIC, LINEAR, enum_count };
69
70const std::vector<std::string> unitNames{"dSpacing", "TOF", "MomentumTransfer"};
71enum class BinUnit { DSPACE, TOF, Q, enum_count };
73
74const size_t NUM_HIST{6}; // TODO make this determined from groupin
75
76// TODO refactor this to use the actual grouping
77double getFocussedPostion(const detid_t detid, const std::vector<double> &difc_focus) {
78 // grouping taken from the IDF for VULCAN
79 if (detid < 0) {
80 throw std::runtime_error("detid < 0 is not supported");
81 } else if (detid < 100000) { // bank1 0-99095
82 return difc_focus[0];
83 } else if (detid < 200000) { // bank2 100000-199095
84 return difc_focus[1];
85 } else if (detid < 300000) { // bank3 200000-289095
86 return difc_focus[2];
87 } else if (detid < 400000) { // bank4 300000-389095
88 return difc_focus[3];
89 } else if (detid < 500000) { // bank5 400000-440095
90 return difc_focus[4];
91 } else if (detid < 600000) { // bank6 500000-554095
92 return difc_focus[5];
93 } else {
94 throw std::runtime_error("detid > 600000 is not supported");
95 }
96}
97
98std::vector<double> calculate_difc_focused(const double l1, const std::vector<double> &l2s,
99 const std::vector<double> &polars) {
100 constexpr double deg2rad = std::numbers::pi_v<double> / 180.;
101
102 std::vector<double> difc;
103
104 std::transform(l2s.cbegin(), l2s.cend(), polars.cbegin(), std::back_inserter(difc),
105 [l1, deg2rad](const auto &l2, const auto &polar) {
106 return 1. / Kernel::Units::tofToDSpacingFactor(l1, l2, deg2rad * polar, 0.);
107 });
108
109 return difc;
110}
111
112} // namespace
113
114// Register the algorithm into the AlgorithmFactory
115DECLARE_ALGORITHM(AlignAndFocusPowderSlim)
116
117//----------------------------------------------------------------------------------------------
118
119
120const std::string AlignAndFocusPowderSlim::name() const { return "AlignAndFocusPowderSlim"; }
121
123int AlignAndFocusPowderSlim::version() const { return 1; }
124
126const std::string AlignAndFocusPowderSlim::category() const { return "Workflow\\Diffraction"; }
127
129const std::string AlignAndFocusPowderSlim::summary() const {
130 return "VULCAN ONLY Algorithm to focus powder diffraction data into a number of histograms according to a grouping "
131 "scheme defined in a CalFile.";
132}
133
134const std::vector<std::string> AlignAndFocusPowderSlim::seeAlso() const { return {"AlignAndFocusPowderFromFiles"}; }
135
136//----------------------------------------------------------------------------------------------
140 const std::vector<std::string> exts{".nxs.h5", ".nxs", "_event.nxs"};
141 // docs copied/modified from LoadEventNexus
142 declareProperty(std::make_unique<FileProperty>(PropertyNames::FILENAME, "", FileProperty::Load, exts),
143 "The name of the Event NeXus file to read, including its full or relative path. "
144 "The file name is typically of the form INST_####_event.nxs.");
148 "To only include events after the provided start time, in seconds (relative to the start of the run).");
149
153 "To only include events before the provided stop time, in seconds (relative to the start of the run).");
156 "Input workspace specifying \"splitters\", i.e. time intervals and targets for event filtering. "
157 "Currently only a single output workspace is supported.");
159 "Flag indicating whether in SplitterWorkspace the times are absolute or "
160 "relative. If true, they are relative to the run start time.");
163 "For development testing. Changes how the splitters are processed. If true then use ProcessBankSplitTask "
164 "otherwise loop over ProcessBankTask.");
166 "Find time-of-flight when neutron was at the sample position. This is only necessary for fast logs "
167 "(i.e. more frequent than proton on target pulse).");
170 "If true, events will be splitting using full time values (tof+pulsetime) rather than just pulsetime.");
171 auto mustBePositive = std::make_shared<BoundedValidator<int>>();
172 mustBePositive->setLower(0);
174 "Filter bad pulses in the same way that :ref:`algm-FilterBadPulses` does.");
175 auto range = std::make_shared<BoundedValidator<double>>();
176 range->setBounds(0., 100.);
178 "The percentage of the average to use as the lower bound when filtering bad pulses.");
179 const std::vector<std::string> cal_exts{".h5", ".hd5", ".hdf", ".cal"};
180 declareProperty(std::make_unique<FileProperty>(PropertyNames::CAL_FILE, "", FileProperty::OptionalLoad, cal_exts),
181 "The .cal file containing the position correction factors. Either this or OffsetsWorkspace needs to "
182 "be specified.");
183 auto mustBePosArr = std::make_shared<Kernel::ArrayBoundedValidator<double>>();
184 mustBePosArr->setLower(0.0);
185 declareProperty(std::make_unique<ArrayProperty<double>>(PropertyNames::X_MIN, std::vector<double>{0.1}, mustBePosArr),
186 "Minimum x-value for the output binning");
187 declareProperty(std::make_unique<ArrayProperty<double>>(PropertyNames::X_DELTA, std::vector<double>{0.0016}),
188 "Bin size for output data");
189 declareProperty(std::make_unique<ArrayProperty<double>>(PropertyNames::X_MAX, std::vector<double>{2.0}, mustBePosArr),
190 "Minimum x-value for the output binning");
192 "The units of the input X min, max and delta values. Output will always be TOF");
194 "Specify binning behavior ('Logarithmic')");
196 "If specified, only these logs will be loaded from the file");
198 "If specified, these logs will not be loaded from the file");
201 "An output workspace.");
202
203 // parameters for chunking options - consider removing these later
204 const std::string CHUNKING_PARAM_GROUP("Chunking-temporary");
205 auto positiveIntValidator = std::make_shared<Mantid::Kernel::BoundedValidator<int>>();
206 positiveIntValidator->setLower(1);
208 positiveIntValidator),
209 "Number of elements of time-of-flight or detector-id to read at a time. This is a maximum");
212 std::make_unique<Kernel::PropertyWithValue<int>>(PropertyNames::EVENTS_PER_THREAD, 1000000, positiveIntValidator),
213 "Number of events to read in a single thread. Higher means less threads are created.");
215
216 // load single spectrum
218 positiveIntValidator),
219 "The bank for which to read data; if specified, others will be blank");
220}
221
222std::map<std::string, std::string> AlignAndFocusPowderSlim::validateInputs() {
223 std::map<std::string, std::string> errors;
224
225 // make sure that data is read in larger chunks than the events processed in a single thread
226 const int disk_chunk = getProperty(PropertyNames::READ_SIZE_FROM_DISK);
227 const int grainsize_events = getProperty(PropertyNames::EVENTS_PER_THREAD);
228 if (disk_chunk < grainsize_events) {
229 const std::string msg(PropertyNames::READ_SIZE_FROM_DISK + " must be larger than " +
233 }
234
235 // validate binning information is consistent with each other
236 const std::vector<double> xmins = getProperty(PropertyNames::X_MIN);
237 const std::vector<double> xmaxs = getProperty(PropertyNames::X_MAX);
238 const std::vector<double> deltas = getProperty(PropertyNames::X_DELTA);
239
240 const auto numMin = xmins.size();
241 const auto numMax = xmaxs.size();
242 const auto numDelta = deltas.size();
243
244 if (std::any_of(deltas.cbegin(), deltas.cend(), [](double d) { return !std::isfinite(d) || d == 0; }))
245 errors[PropertyNames::X_DELTA] = "All must be nonzero";
246 else if (!(numDelta == 1 || numDelta == NUM_HIST))
247 errors[PropertyNames::X_DELTA] = "Must have 1 or 6 values";
248
249 if (!(numMin == 1 || numMin == NUM_HIST))
250 errors[PropertyNames::X_MIN] = "Must have 1 or 6 values";
251
252 if (!(numMax == 1 || numMax == NUM_HIST))
253 errors[PropertyNames::X_MAX] = "Must have 1 or 6 values";
254
255 // only specify allow or block list for logs
257 errors[PropertyNames::ALLOW_LOGS] = "Cannot specify both allow and block lists";
258 errors[PropertyNames::BLOCK_LOGS] = "Cannot specify both allow and block lists";
259 }
260
261 return errors;
262}
263
264//----------------------------------------------------------------------------------------------
268 // create a histogram workspace
269
270 // These give the limits in each file as to which events we actually load (when filtering by time).
271 loadStart.resize(1, 0);
272 loadSize.resize(1, 0);
273
274 this->progress(.0, "Create output workspace");
275
277
278 const std::string filename = getPropertyValue(PropertyNames::FILENAME);
279 { // TODO TEMPORARY - this algorithm is hard coded for VULCAN
280 // it needs to be made more generic
281 if (filename.find("VULCAN") == std::string::npos) {
282 throw std::runtime_error("File does not appear to be for VULCAN");
283 }
284 }
285 const Nexus::NexusDescriptor descriptor(filename);
286
287 // instrument is needed for lots of things
288 const std::string ENTRY_TOP_LEVEL("entry");
289 LoadEventNexus::loadInstrument<MatrixWorkspace_sptr>(filename, wksp, ENTRY_TOP_LEVEL, this, &descriptor);
290
291 // TODO parameters should be input information
292 const double l1{43.755};
293 const std::vector<double> polars{90, 90, 120, 150, 157, 65.5}; // two-theta
294 const std::vector<double> azimuthals{180, 0, 0, 0, 0, 0}; // angle from positive x-axis
295 const std::vector<double> l2s{2.296, 2.296, 2.070, 2.070, 2.070, 2.530};
296 const std::vector<specnum_t> specids;
297 const auto difc_focused = calculate_difc_focused(l1, l2s, polars);
298
299 // create values for focusing time-of-flight
300 this->progress(.05, "Creating calibration constants");
301 const std::string cal_filename = getPropertyValue(PropertyNames::CAL_FILE);
302 if (!cal_filename.empty()) {
303 this->loadCalFile(wksp, cal_filename, difc_focused);
304 } else {
305 this->initCalibrationConstants(wksp, difc_focused);
306 }
307
308 // calculate correction for tof of the neutron at the sample position
310 this->initScaleAtSample(wksp);
311 }
312
313 // set the instrument
314 this->progress(.07, "Set instrument geometry");
315 wksp = this->editInstrumentGeometry(wksp, l1, polars, specids, l2s, azimuthals);
316
317 // convert to TOF if not already
318 this->progress(.1, "Convert bins to TOF");
319 wksp = this->convertToTOF(wksp);
320
321 /* TODO create grouping information
322 // create IndexInfo
323 // prog->doReport("Creating IndexInfo"); TODO add progress bar stuff
324 const std::vector<int32_t> range;
325 LoadEventNexusIndexSetup indexSetup(WS, EMPTY_INT(), EMPTY_INT(), range);
326 auto indexInfo = indexSetup.makeIndexInfo();
327 const size_t numHist = indexInfo.size();
328 */
329
330 // load run metadata
331 this->progress(.11, "Loading metadata");
332 // prog->doReport("Loading metadata"); TODO add progress bar stuff
333 try {
334 LoadEventNexus::loadEntryMetadata(filename, wksp, ENTRY_TOP_LEVEL, descriptor);
335 } catch (std::exception &e) {
336 g_log.warning() << "Error while loading meta data: " << e.what() << '\n';
337 }
338
339 // load logs
340 this->progress(.12, "Loading logs");
341 auto periodLog = std::make_unique<const TimeSeriesProperty<int>>("period_log"); // not used
342 const std::vector<std::string> &allow_logs = getProperty(PropertyNames::ALLOW_LOGS);
343 const std::vector<std::string> &block_logs = getProperty(PropertyNames::BLOCK_LOGS);
344 int nPeriods{1};
345 LoadEventNexus::runLoadNexusLogs<MatrixWorkspace_sptr>(filename, wksp, *this, false, nPeriods, periodLog, allow_logs,
346 block_logs);
347
348 const auto timeSplitter = this->timeSplitterFromSplitterWorkspace(wksp->run().startTime());
349 const auto filterROI = this->getFilterROI(wksp);
350 // determine the pulse indices from the time and splitter workspace
351 this->progress(.15, "Determining pulse indices");
352
353 // Now we want to go through all the bankN_event entries
354 const std::map<std::string, std::set<std::string>> &allEntries = descriptor.getAllEntries();
355 auto itClassEntries = allEntries.find("NXevent_data");
356
357 // load the events
358 H5::H5File h5file(filename, H5F_ACC_RDONLY, Nexus::H5Util::defaultFileAcc());
359 if (itClassEntries == allEntries.end()) {
360 h5file.close();
361 throw std::runtime_error("No NXevent_data entries found in file");
362 }
363
364 this->progress(.17, "Reading events");
365
366 // hard coded for VULCAN 6 banks
367 std::vector<std::string> bankEntryNames;
368 std::size_t num_banks_to_read;
369 int outputSpecNum = getProperty(PropertyNames::OUTPUT_SPEC_NUM);
370 if (outputSpecNum == EMPTY_INT()) {
371 for (size_t i = 1; i <= NUM_HIST; ++i) {
372 bankEntryNames.push_back("bank" + std::to_string(i) + "_events");
373 }
374 num_banks_to_read = NUM_HIST;
375 } else {
376 // fill this vector with blanks -- this is for the ProcessBankTask to correctly access it
377 for (size_t i = 1; i <= NUM_HIST; ++i) {
378 bankEntryNames.push_back("");
379 }
380 // the desired bank gets the correct entry name
381 bankEntryNames[outputSpecNum - 1] = "bank" + std::to_string(outputSpecNum) + "_events";
382 num_banks_to_read = 1;
383 }
384
385 // threaded processing of the banks
386 const int DISK_CHUNK = getProperty(PropertyNames::READ_SIZE_FROM_DISK);
387 const int GRAINSIZE_EVENTS = getProperty(PropertyNames::EVENTS_PER_THREAD);
388 g_log.debug() << (DISK_CHUNK / GRAINSIZE_EVENTS) << " threads per chunk\n";
389
390 if (timeSplitter.empty()) {
391 const auto pulse_indices = this->determinePulseIndices(wksp, filterROI);
392
393 auto progress = std::make_shared<API::Progress>(this, .17, .9, num_banks_to_read);
394 ProcessBankTask task(bankEntryNames, h5file, is_time_filtered, wksp, m_calibration, m_scale_at_sample, m_masked,
395 static_cast<size_t>(DISK_CHUNK), static_cast<size_t>(GRAINSIZE_EVENTS), pulse_indices,
396 progress);
397 // generate threads only if appropriate
398 if (num_banks_to_read > 1) {
399 tbb::parallel_for(tbb::blocked_range<size_t>(0, num_banks_to_read), task);
400 } else {
401 // a "range" of 1; note -1 to match 0-indexed array with 1-indexed bank labels
402 task(tbb::blocked_range<size_t>(outputSpecNum - 1, outputSpecNum));
403 }
404
405 // close the file so child algorithms can do their thing
406 h5file.close();
407
408 // update the run TimeROI and remove log data outside the time ROI
409 wksp->mutableRun().setTimeROI(filterROI);
410 wksp->mutableRun().removeDataOutsideTimeROI();
411
412 setProperty(PropertyNames::OUTPUT_WKSP, std::move(wksp));
413 } else {
414 std::string ws_basename = this->getPropertyValue(PropertyNames::OUTPUT_WKSP);
415 std::vector<std::string> wsNames;
416 std::vector<int> workspaceIndices;
417 std::vector<MatrixWorkspace_sptr> workspaces;
418 for (const int &splitter_target : timeSplitter.outputWorkspaceIndices()) {
419 std::string ws_name = ws_basename + "_" + timeSplitter.getWorkspaceIndexName(splitter_target);
420 wsNames.push_back(std::move(ws_name));
421 workspaceIndices.push_back(splitter_target);
422 workspaces.emplace_back(wksp->clone());
423 }
424
425 auto progress = std::make_shared<API::Progress>(this, .17, .9, num_banks_to_read * workspaceIndices.size());
427 g_log.information() << "Using ProcessBankSplitFullTimeTask for splitter processing\n";
428
429 // Get the combined time ROI for all targets so we only load necessary events.
430 // Need to offset the start time to account for tof's greater than pulsetime. 66.6ms is 4 pulses.
431 auto combined_time_roi = timeSplitter.combinedTimeROI(PULSETIME_OFFSET);
432 if (!filterROI.useAll()) {
433 combined_time_roi.update_intersection(filterROI);
434 }
435
436 const auto pulse_indices = this->determinePulseIndices(wksp, combined_time_roi);
437
438 const auto &splitterMap = timeSplitter.getSplittersMap();
439
440 ProcessBankSplitFullTimeTask task(bankEntryNames, h5file, is_time_filtered, workspaceIndices, workspaces,
441 m_calibration, m_scale_at_sample, m_masked, static_cast<size_t>(DISK_CHUNK),
442 static_cast<size_t>(GRAINSIZE_EVENTS), pulse_indices, splitterMap, progress);
443
444 // generate threads only if appropriate
445 if (num_banks_to_read > 1) {
446 tbb::parallel_for(tbb::blocked_range<size_t>(0, num_banks_to_read), task);
447 } else {
448 // a "range" of 1; note -1 to match 0-indexed array with 1-indexed bank labels
449 task(tbb::blocked_range<size_t>(outputSpecNum - 1, outputSpecNum));
450 }
451
453 g_log.information() << "Using ProcessBankSplitTask for splitter processing\n";
454 // determine the pulse indices from the time and splitter workspace
455 const auto target_to_pulse_indices = this->determinePulseIndicesTargets(wksp, filterROI, timeSplitter);
456
457 ProcessBankSplitTask task(bankEntryNames, h5file, true, workspaceIndices, workspaces, m_calibration,
458 m_scale_at_sample, m_masked, static_cast<size_t>(DISK_CHUNK),
459 static_cast<size_t>(GRAINSIZE_EVENTS), target_to_pulse_indices, progress);
460 // generate threads only if appropriate
461 if (num_banks_to_read > 1) {
462 tbb::parallel_for(tbb::blocked_range<size_t>(0, num_banks_to_read), task);
463 } else {
464 // a "range" of 1; note -1 to match 0-indexed array with 1-indexed bank labels
465 task(tbb::blocked_range<size_t>(outputSpecNum - 1, outputSpecNum));
466 }
467 } else {
468 g_log.information() << "Using ProcessBankTask for splitter processing\n";
469 // loop over the targets in the splitter workspace, each target gets its own output workspace
470 tbb::parallel_for(
471 tbb::blocked_range<size_t>(0, workspaceIndices.size()),
472 [&](const tbb::blocked_range<size_t> &target_indices) {
473 for (size_t target_index = target_indices.begin(); target_index != target_indices.end(); ++target_index) {
474 const int splitter_target = workspaceIndices[target_index];
475
476 auto splitter_roi = timeSplitter.getTimeROI(splitter_target);
477 // copy the roi so we can modify it just for this target
478 auto target_roi = filterROI;
479 if (target_roi.useAll())
480 target_roi = std::move(splitter_roi); // use the splitter ROI if no time filtering is specified
481 else if (!splitter_roi.useAll())
482 target_roi.update_intersection(splitter_roi); // otherwise intersect with the splitter ROI
483
484 // clone wksp for this target
485 MatrixWorkspace_sptr target_wksp = workspaces[target_index];
486
487 const auto pulse_indices = this->determinePulseIndices(target_wksp, target_roi);
488
489 ProcessBankTask task(bankEntryNames, h5file, is_time_filtered, target_wksp, m_calibration,
490 m_scale_at_sample, m_masked, static_cast<size_t>(DISK_CHUNK),
491 static_cast<size_t>(GRAINSIZE_EVENTS), pulse_indices, progress);
492 // generate threads only if appropriate
493 if (num_banks_to_read > 1) {
494 tbb::parallel_for(tbb::blocked_range<size_t>(0, num_banks_to_read), task);
495 } else {
496 // a "range" of 1; note -1 to match 0-indexed array with 1-indexed bank labels
497 task(tbb::blocked_range<size_t>(outputSpecNum - 1, outputSpecNum));
498 }
499 }
500 });
501 }
502
503 // close the file so child algorithms can do their thing
504 h5file.close();
505
506 // add the workspaces to the ADS
507 for (size_t idx = 0; idx < workspaceIndices.size(); ++idx) {
508 // create the target time ROI combining the splitter and filter ROIs
509 auto target_roi = timeSplitter.getTimeROI(workspaceIndices[idx]);
510 if (target_roi.useAll())
511 target_roi = filterROI; // use the splitter ROI if no time filtering is specified
512 else if (!filterROI.useAll())
513 target_roi.update_intersection(filterROI); // otherwise intersect with the splitter ROI
514
515 // update the run TimeROI and remove log data outside the time ROI
516 workspaces[idx]->mutableRun().setTimeROI(target_roi);
517 workspaces[idx]->mutableRun().removeDataOutsideTimeROI();
518 AnalysisDataService::Instance().addOrReplace(wsNames[idx], workspaces[idx]);
519 }
520
521 // group the workspaces
522 auto groupws = createChildAlgorithm("GroupWorkspaces", 0.95, 1.00, true);
523 groupws->setAlwaysStoreInADS(true);
524 groupws->setProperty("InputWorkspaces", wsNames);
525 groupws->setProperty("OutputWorkspace", ws_basename);
526 groupws->execute();
527
528 if (!groupws->isExecuted()) {
529 throw std::runtime_error("Failed to group output workspaces");
530 }
531
532 API::Workspace_sptr outputWorkspace = AnalysisDataService::Instance().retrieveWS<API::Workspace>(ws_basename);
533
534 setProperty(PropertyNames::OUTPUT_WKSP, outputWorkspace);
535 }
536}
537
538MatrixWorkspace_sptr AlignAndFocusPowderSlim::createOutputWorkspace() {
539 // set up the output workspace binning
540 const BINMODE binmode = getPropertyValue(PropertyNames::BINMODE);
541 const bool linearBins = bool(binmode == BinningMode::LINEAR);
542 const std::string binUnits = getPropertyValue(PropertyNames::BIN_UNITS);
543 std::vector<double> x_delta = getProperty(PropertyNames::X_DELTA);
544 std::vector<double> x_min = getProperty(PropertyNames::X_MIN);
545 std::vector<double> x_max = getProperty(PropertyNames::X_MAX);
546 const bool raggedBins = (x_delta.size() != 1 || x_min.size() != 1 || x_max.size() != 1);
547
548 constexpr bool resize_xnew{true};
549 constexpr bool full_bins_only{false};
550
551 // always use the first histogram x-values for initialization
552 HistogramData::BinEdges XValues(0);
553 if (linearBins) {
554 const std::vector<double> params{x_min[0], x_delta[0], x_max[0]};
556 Kernel::VectorHelper::createAxisFromRebinParams(params, XValues.mutableRawData(), resize_xnew, full_bins_only));
557 } else {
558 const std::vector<double> params{x_min[0], -1. * x_delta[0], x_max[0]};
560 Kernel::VectorHelper::createAxisFromRebinParams(params, XValues.mutableRawData(), resize_xnew, full_bins_only));
561 }
562 MatrixWorkspace_sptr wksp = Mantid::DataObjects::create<Workspace2D>(NUM_HIST, XValues);
563
564 if (raggedBins) {
565 // if ragged bins, we need to resize the x-values for each histogram after the first one
566 if (x_delta.size() == 1)
567 x_delta.resize(NUM_HIST, x_delta[0]);
568 if (x_min.size() == 1)
569 x_min.resize(NUM_HIST, x_min[0]);
570 if (x_max.size() == 1)
571 x_max.resize(NUM_HIST, x_max[0]);
572
573 for (size_t i = 1; i < NUM_HIST; ++i) {
574 HistogramData::BinEdges XValues_new(0);
575
576 if (linearBins) {
577 const std::vector<double> params{x_min[i], x_delta[i], x_max[i]};
578 Kernel::VectorHelper::createAxisFromRebinParams(params, XValues_new.mutableRawData(), resize_xnew,
579 full_bins_only);
580 } else {
581 const std::vector<double> params{x_min[i], -1. * x_delta[i], x_max[i]};
582 Kernel::VectorHelper::createAxisFromRebinParams(params, XValues_new.mutableRawData(), resize_xnew,
583 full_bins_only);
584 }
585 HistogramData::Histogram hist(XValues_new, HistogramData::Counts(XValues_new.size() - 1, 0.0));
586 wksp->setHistogram(i, hist);
587 }
588 }
589
590 wksp->getAxis(0)->setUnit(binUnits);
591 wksp->setYUnit("Counts");
592
593 return wksp;
594}
595
596void AlignAndFocusPowderSlim::initCalibrationConstants(API::MatrixWorkspace_sptr &wksp,
597 const std::vector<double> &difc_focus) {
598 const auto detInfo = wksp->detectorInfo();
599
600 for (auto iter = detInfo.cbegin(); iter != detInfo.cend(); ++iter) {
601 if (!iter->isMonitor()) {
602 const auto difc_focussed = getFocussedPostion(static_cast<detid_t>(iter->detid()), difc_focus);
603 m_calibration.emplace(static_cast<detid_t>(iter->detid()),
604 difc_focussed / detInfo.difcUncalibrated(iter->index()));
605 }
606 }
607}
608
609void AlignAndFocusPowderSlim::loadCalFile(const Mantid::API::Workspace_sptr &inputWS, const std::string &filename,
610 const std::vector<double> &difc_focus) {
611 auto alg = createChildAlgorithm("LoadDiffCal");
612 alg->setProperty("InputWorkspace", inputWS);
613 alg->setPropertyValue("Filename", filename);
614 alg->setProperty<bool>("MakeCalWorkspace", true);
615 alg->setProperty<bool>("MakeGroupingWorkspace", false);
616 alg->setProperty<bool>("MakeMaskWorkspace", true);
617 alg->setPropertyValue("WorkspaceName", "temp");
618 alg->executeAsChildAlg();
619
620 const ITableWorkspace_sptr calibrationWS = alg->getProperty("OutputCalWorkspace");
621 for (size_t row = 0; row < calibrationWS->rowCount(); ++row) {
622 const detid_t detid = calibrationWS->cell<int>(row, 0);
623 const double detc = calibrationWS->cell<double>(row, 1);
624 const auto difc_focussed = getFocussedPostion(detid, difc_focus);
625 m_calibration.emplace(detid, difc_focussed / detc);
626 }
627
628 const MaskWorkspace_sptr maskWS = alg->getProperty("OutputMaskWorkspace");
629 m_masked = maskWS->getMaskedDetectors();
630 g_log.debug() << "Masked detectors: " << m_masked.size() << '\n';
631}
632
637void AlignAndFocusPowderSlim::initScaleAtSample(const API::MatrixWorkspace_sptr &wksp) {
638 // detector information for all of the L2
639 const auto detInfo = wksp->detectorInfo();
640 // cache a single L1 value
641 const double L1 = detInfo.l1();
642
643 if (this->getProperty(PropertyNames::CORRECTION_TO_SAMPLE)) {
644 // calculate scale factors for each detector
645 for (auto iter = detInfo.cbegin(); iter != detInfo.cend(); ++iter) {
646 if (!iter->isMonitor()) {
647 const double path_correction = L1 / (L1 + iter->l2()) * 1000.0;
648 m_scale_at_sample.emplace(static_cast<detid_t>(iter->detid()), path_correction);
649 }
650 }
651 } else {
652 // set all scale factors to 1.0
653 for (auto iter = detInfo.cbegin(); iter != detInfo.cend(); ++iter) {
654 if (!iter->isMonitor()) {
655 m_scale_at_sample.emplace(static_cast<detid_t>(iter->detid()), 1000.0);
656 }
657 }
658 }
659}
660
661API::MatrixWorkspace_sptr AlignAndFocusPowderSlim::editInstrumentGeometry(
662 API::MatrixWorkspace_sptr &wksp, const double l1, const std::vector<double> &polars,
663 const std::vector<specnum_t> &specids, const std::vector<double> &l2s, const std::vector<double> &azimuthals) {
664 API::IAlgorithm_sptr editAlg = createChildAlgorithm("EditInstrumentGeometry");
665 editAlg->setLoggingOffset(1);
666 editAlg->setProperty("Workspace", wksp);
667 if (l1 > 0.)
668 editAlg->setProperty("PrimaryFlightPath", l1);
669 if (!polars.empty())
670 editAlg->setProperty("Polar", polars);
671 if (!specids.empty())
672 editAlg->setProperty("SpectrumIDs", specids);
673 if (!l2s.empty())
674 editAlg->setProperty("L2", l2s);
675 if (!azimuthals.empty())
676 editAlg->setProperty("Azimuthal", azimuthals);
677 editAlg->executeAsChildAlg();
678
679 wksp = editAlg->getProperty("Workspace");
680
681 return wksp;
682}
683
684API::MatrixWorkspace_sptr AlignAndFocusPowderSlim::convertToTOF(API::MatrixWorkspace_sptr &wksp) {
685 if (wksp->getAxis(0)->unit()->unitID() == "TOF") {
686 // already in TOF, no need to convert
687 return wksp;
688 }
689
690 API::IAlgorithm_sptr convertUnits = createChildAlgorithm("ConvertUnits");
691 convertUnits->setProperty("InputWorkspace", wksp);
692 convertUnits->setPropertyValue("Target", "TOF");
693 convertUnits->executeAsChildAlg();
694 wksp = convertUnits->getProperty("OutputWorkspace");
695
696 return wksp;
697}
698
706Kernel::TimeROI AlignAndFocusPowderSlim::getFilterROI(const API::MatrixWorkspace_sptr &wksp) {
707 Kernel::TimeROI roi;
708 const auto startOfRun = wksp->run().startTime();
709
710 // filter by time
711 double filter_time_start_sec = getProperty(PropertyNames::FILTER_TIMESTART);
712 double filter_time_stop_sec = getProperty(PropertyNames::FILTER_TIMESTOP);
713 if (filter_time_start_sec != EMPTY_DBL() || filter_time_stop_sec != EMPTY_DBL()) {
714 this->progress(.15, "Creating time filtering");
715 g_log.information() << "Filtering pulses from " << filter_time_start_sec << " to " << filter_time_stop_sec << "s\n";
716
717 try {
718 roi.addROI(startOfRun + (filter_time_start_sec == EMPTY_DBL() ? 0.0 : filter_time_start_sec),
719 startOfRun + filter_time_stop_sec); // start and stop times in seconds
720 } catch (const std::runtime_error &e) {
721 throw std::invalid_argument("Invalid time range for filtering: " + std::string(e.what()));
722 }
723 }
724
725 // filter bad pulses
726 if (getProperty(PropertyNames::FILTER_BAD_PULSES)) {
727 this->progress(.16, "Filtering bad pulses");
728
729 // get limits from proton_charge
730 const auto [min_pcharge, max_pcharge, mean] =
731 wksp->run().getBadPulseRange(LOG_CHARGE_NAME, getProperty(PropertyNames::FILTER_BAD_PULSES_LOWER_CUTOFF));
732 g_log.information() << "Filtering bad pulses; pcharge outside of " << min_pcharge << " to " << max_pcharge << '\n';
733
734 const auto run_start = wksp->getFirstPulseTime();
735 const auto run_stop = wksp->getLastPulseTime();
736
737 const auto log = dynamic_cast<const TimeSeriesProperty<double> *>(wksp->run().getLogData(LOG_CHARGE_NAME));
738 if (log) {
739 // need to have centre=true for proton_charge
740 roi = log->makeFilterByValue(min_pcharge, max_pcharge, true, Mantid::Kernel::TimeInterval(run_start, run_stop),
741 0.0, true, &roi);
742 }
743 }
744 return roi;
745}
746
754std::vector<PulseROI> AlignAndFocusPowderSlim::determinePulseIndices(const API::MatrixWorkspace_sptr &wksp,
755 const TimeROI &filterROI) {
756
757 std::vector<PulseROI> pulse_indices;
758 if (filterROI.useAll()) {
759 pulse_indices.emplace_back(0, std::numeric_limits<size_t>::max());
760 } else {
761 is_time_filtered = true;
762
763 // get pulse times from frequency log on workspace
764 const auto frequency_log = dynamic_cast<const TimeSeriesProperty<double> *>(wksp->run().getProperty("frequency"));
765 if (!frequency_log) {
766 throw std::runtime_error("Frequency log not found in workspace run");
767 }
768 const auto pulse_times =
769 std::make_unique<std::vector<Mantid::Types::Core::DateAndTime>>(frequency_log->timesAsVector());
770
771 pulse_indices = filterROI.calculate_indices(*pulse_times);
772 if (pulse_indices.empty())
773 throw std::invalid_argument("No valid pulse time indices found for filtering");
774 }
775
776 return pulse_indices;
777}
778
788std::vector<std::pair<int, PulseROI>>
789AlignAndFocusPowderSlim::determinePulseIndicesTargets(const API::MatrixWorkspace_sptr &wksp, const TimeROI &filterROI,
790 const TimeSplitter &timeSplitter) {
791 // get pulse times from frequency log on workspace
792 const auto frequency_log = dynamic_cast<const TimeSeriesProperty<double> *>(wksp->run().getProperty("frequency"));
793 if (!frequency_log) {
794 throw std::runtime_error("Frequency log not found in workspace run");
795 }
796 const auto pulse_times =
797 std::make_unique<std::vector<Mantid::Types::Core::DateAndTime>>(frequency_log->timesAsVector());
798
799 std::vector<PulseROI> pulse_indices;
800 if (filterROI.useAll()) {
801 pulse_indices.emplace_back(0, std::numeric_limits<size_t>::max());
802 } else {
803 pulse_indices = filterROI.calculate_indices(*pulse_times);
804 if (pulse_indices.empty())
805 throw std::invalid_argument("No valid pulse time indices found for filtering");
806 }
807
808 const auto target_to_pulse_indices = timeSplitter.calculate_target_indices(*pulse_times);
809
810 // calculate intersection of target pulse indices and time filter pulse indices (removes pulses outside filterROI)
811 std::vector<std::pair<int, PulseROI>> intersected_target_pulse_indices;
812 auto pulse_it = pulse_indices.cbegin();
813 for (const auto &target_pair : target_to_pulse_indices) {
814 // move pulse_it to the first pulse that could overlap
815 while (pulse_it != pulse_indices.cend() && pulse_it->second <= target_pair.second.first) {
816 ++pulse_it;
817 }
818 // check for overlaps
819 auto check_it = pulse_it;
820 while (check_it != pulse_indices.cend() && check_it->first < target_pair.second.second) {
821 // there is an overlap
822 size_t start_index = std::max(check_it->first, target_pair.second.first);
823 size_t stop_index = std::min(check_it->second, target_pair.second.second);
824 if (start_index < stop_index) {
825 intersected_target_pulse_indices.emplace_back(target_pair.first, PulseROI(start_index, stop_index));
826 }
827 ++check_it;
828 }
829 }
830
831 return intersected_target_pulse_indices;
832}
833
835AlignAndFocusPowderSlim::timeSplitterFromSplitterWorkspace(const Types::Core::DateAndTime &filterStartTime) {
836 API::Workspace_sptr tempws = this->getProperty(PropertyNames::SPLITTER_WS);
837 DataObjects::SplittersWorkspace_sptr splittersWorkspace =
838 std::dynamic_pointer_cast<DataObjects::SplittersWorkspace>(tempws);
839 DataObjects::TableWorkspace_sptr splitterTableWorkspace =
840 std::dynamic_pointer_cast<DataObjects::TableWorkspace>(tempws);
841 API::MatrixWorkspace_sptr matrixSplitterWS = std::dynamic_pointer_cast<API::MatrixWorkspace>(tempws);
842
843 if (!splittersWorkspace && !splitterTableWorkspace && !matrixSplitterWS)
844 return {};
845
846 const bool isSplittersRelativeTime = this->getProperty(PropertyNames::SPLITTER_RELATIVE);
847
848 TimeSplitter time_splitter;
849 if (splittersWorkspace) {
850 time_splitter = TimeSplitter{splittersWorkspace};
851 } else if (splitterTableWorkspace) {
852 time_splitter =
853 TimeSplitter(splitterTableWorkspace, isSplittersRelativeTime ? filterStartTime : DateAndTime::GPS_EPOCH);
854 } else {
855 time_splitter = TimeSplitter(matrixSplitterWS, isSplittersRelativeTime ? filterStartTime : DateAndTime::GPS_EPOCH);
856 }
857
858 return time_splitter;
859}
860
861} // namespace Mantid::DataHandling::AlignAndFocusPowderSlim
std::string name
Definition Run.cpp:60
#define DECLARE_ALGORITHM(classname)
Definition Algorithm.h:538
#define UNUSED_ARG(x)
Function arguments are sometimes unused in certain implmentations but are required for documentation ...
Definition System.h:48
void declareProperty(std::unique_ptr< Kernel::Property > p, const std::string &doc="") override
Add a property to the list of managed properties.
std::string getPropertyValue(const std::string &name) const override
Get the value of a property as a string.
TypedValue getProperty(const std::string &name) const override
Get the value of a property.
virtual std::shared_ptr< Algorithm > createChildAlgorithm(const std::string &name, const double startProgress=-1., const double endProgress=-1., const bool enableLogging=true, const int &version=-1)
Create a Child Algorithm.
Kernel::Logger & g_log
Definition Algorithm.h:422
void progress(double p, const std::string &msg="", double estimatedTime=0.0, int progressPrecision=0)
Sends ProgressNotification.
bool isDefault(const std::string &name) const
A specialized class for dealing with file properties.
@ OptionalLoad
to specify a file to read but the file doesn't have to exist
@ Load
allowed here which will be passed to the algorithm
A property class for workspaces.
Base Workspace Abstract Class.
Definition Workspace.h:29
const std::string summary() const override
Algorithm's summary for use in the GUI and help.
std::map< std::string, std::string > validateInputs() override
Perform validation of ALL the input properties of the algorithm.
std::vector< int64_t > loadStart
Index to load start at in the file.
DataObjects::TimeSplitter timeSplitterFromSplitterWorkspace(const Types::Core::DateAndTime &)
std::map< detid_t, double > m_scale_at_sample
Multiplicative 0<value<1 to move neutron TOF at sample.
void initScaleAtSample(const API::MatrixWorkspace_sptr &wksp)
For fast logs, calculate the sample position correction.
const std::string category() const override
Algorithm's category for identification.
static std::vector< std::pair< int, std::pair< size_t, size_t > > > determinePulseIndicesTargets(const API::MatrixWorkspace_sptr &wksp, const Kernel::TimeROI &filterROI, const DataObjects::TimeSplitter &timeSplitter)
Determine the pulse indices for a given workspace, time ROI, and time splitter.
API::MatrixWorkspace_sptr editInstrumentGeometry(API::MatrixWorkspace_sptr &wksp, const double l1, const std::vector< double > &polars, const std::vector< specnum_t > &specids, const std::vector< double > &l2s, const std::vector< double > &azimuthals)
std::vector< std::pair< size_t, size_t > > determinePulseIndices(const API::MatrixWorkspace_sptr &wksp, const Kernel::TimeROI &filterROI)
Determine the pulse indices for a given workspace and time ROI.
int version() const override
Algorithm's version for identification.
const std::vector< std::string > seeAlso() const override
Function to return all of the seeAlso (these are not validated) algorithms related to this algorithm....
void loadCalFile(const API::Workspace_sptr &inputWS, const std::string &filename, const std::vector< double > &difc_focus)
Kernel::TimeROI getFilterROI(const API::MatrixWorkspace_sptr &wksp)
Create a TimeROI based on the filtering properties set in the algorithm.
API::MatrixWorkspace_sptr convertToTOF(API::MatrixWorkspace_sptr &wksp)
void initCalibrationConstants(API::MatrixWorkspace_sptr &wksp, const std::vector< double > &difc_focus)
static void loadEntryMetadata(const std::string &nexusfilename, T WS, const std::string &entry_name, const Nexus::NexusDescriptor &descriptor)
Load the run number and other meta data from the given bank.
std::vector< std::pair< int, std::pair< size_t, size_t > > > calculate_target_indices(const std::vector< DateAndTime > &times) const
Given a list of times, calculate the corresponding indices in the TimeSplitter.
Concrete workspace implementation.
Definition Workspace2D.h:29
Kernel/ArrayBoundedValidator.h.
Support for a property that holds an array of values.
BoundedValidator is a validator that requires the values to be between upper or lower bounds,...
A concrete property based on user options of a finite list of strings.
IPropertyManager * setProperty(const std::string &name, const T &value)
Templated method to set the value of a PropertyWithValue.
void setPropertyGroup(const std::string &name, const std::string &group)
Set the group for a given property.
void debug(const std::string &msg)
Logs at debug level.
Definition Logger.cpp:145
void warning(const std::string &msg)
Logs at warning level.
Definition Logger.cpp:117
void information(const std::string &msg)
Logs at information level.
Definition Logger.cpp:136
The concrete, templated class for properties.
Represents a time interval.
Definition DateAndTime.h:25
TimeROI : Object that holds information about when the time measurement was active.
Definition TimeROI.h:18
std::vector< std::pair< size_t, size_t > > calculate_indices(const std::vector< Types::Core::DateAndTime > &times) const
Definition TimeROI.cpp:346
void addROI(const std::string &startTime, const std::string &stopTime)
Definition TimeROI.cpp:76
bool useAll() const
TimeROI selects all time to be used.
Definition TimeROI.cpp:693
A specialised Property class for holding a series of time-value pairs.
const std::map< std::string, std::set< std::string > > & getAllEntries() const noexcept
Returns a const reference of the internal map holding all entries in the Nexus HDF5 file.
std::shared_ptr< IAlgorithm > IAlgorithm_sptr
shared pointer to Mantid::API::IAlgorithm
std::shared_ptr< ITableWorkspace > ITableWorkspace_sptr
shared pointer to Mantid::API::ITableWorkspace
std::shared_ptr< Workspace > Workspace_sptr
shared pointer to Mantid::API::Workspace
Mantid::Kernel::SingletonHolder< AnalysisDataServiceImpl > AnalysisDataService
Kernel::Logger g_log("ExperimentInfo")
static logger object
std::shared_ptr< MatrixWorkspace > MatrixWorkspace_sptr
shared pointer to the matrix workspace base class
const std::string FILTER_TIMESTART("FilterByTimeStart")
const std::string FILTER_BAD_PULSES_LOWER_CUTOFF("BadPulsesLowerCutoff")
const std::string OUTPUT_SPEC_NUM("OutputSpectrumNumber")
const std::string PROCESS_BANK_SPLIT_TASK("ProcessBankSplitTask")
const std::string CORRECTION_TO_SAMPLE("CorrectionToSample")
const std::string FILTER_TIMESTOP("FilterByTimeStop")
const std::string SPLITTER_WS("SplitterWorkspace")
const std::string EVENTS_PER_THREAD("EventsPerThread")
const std::string FILTER_BAD_PULSES("FilterBadPulses")
const std::string READ_SIZE_FROM_DISK("ReadSizeFromDisk")
std::shared_ptr< SplittersWorkspace > SplittersWorkspace_sptr
std::shared_ptr< TableWorkspace > TableWorkspace_sptr
shared pointer to Mantid::DataObjects::TableWorkspace
std::shared_ptr< MaskWorkspace > MaskWorkspace_sptr
shared pointer to the MaskWorkspace class
constexpr double deg2rad
Defines units/enum for Crystal work.
Definition AngleUnits.h:20
int MANTID_KERNEL_DLL createAxisFromRebinParams(const std::vector< double > &params, std::vector< double > &xnew, const bool resize_xnew=true, const bool full_bins_only=false, const double xMinHint=std::nan(""), const double xMaxHint=std::nan(""), const bool useReverseLogarithmic=false, const double power=-1)
Creates a new output X array given a 'standard' set of rebinning parameters.
MANTID_NEXUS_DLL H5::FileAccPropList defaultFileAcc()
Default file access is H5F_CLOSE_STRONG.
Definition H5Util.cpp:119
const std::string BINMODE("BinningMode")
constexpr int EMPTY_INT() noexcept
Returns what we consider an "empty" integer within a property.
Definition EmptyValues.h:24
int32_t detid_t
Typedef for a detector ID.
constexpr double EMPTY_DBL() noexcept
Returns what we consider an "empty" double within a property.
Definition EmptyValues.h:42
STL namespace.
std::string to_string(const wide_integer< Bits, Signed > &n)
Describes the direction (within an algorithm) of a Property.
Definition Property.h:50
@ Input
An input workspace.
Definition Property.h:53
@ Output
An output workspace.
Definition Property.h:54