Mantid
Loading...
Searching...
No Matches
Integration.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2018 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//----------------------------------------------------------------------
8// Includes
9//----------------------------------------------------------------------
12#include "MantidAPI/TextAxis.h"
18#include "MantidHistogramData/Histogram.h"
22
23#include <cmath>
24#include <numeric>
25
26namespace Mantid::Algorithms {
27
28// Register the class into the algorithm factory
29DECLARE_ALGORITHM(Integration)
30
31using namespace Kernel;
32using namespace API;
33using namespace DataObjects;
34using namespace HistogramData;
35
40 declareProperty(std::make_unique<WorkspaceProperty<>>("InputWorkspace", "", Direction::Input),
41 "The input workspace to integrate.");
42 declareProperty(std::make_unique<WorkspaceProperty<>>("OutputWorkspace", "", Direction::Output),
43 "The output workspace with the results of the integration.");
44
45 declareProperty("RangeLower", EMPTY_DBL(), "The lower integration limit (an X value).");
46 declareProperty("RangeUpper", EMPTY_DBL(), "The upper integration limit (an X value).");
47
48 auto mustBePositive = std::make_shared<BoundedValidator<int>>();
49 mustBePositive->setLower(0);
50 declareProperty("StartWorkspaceIndex", 0, mustBePositive, "Index of the first spectrum to integrate.");
51 declareProperty("EndWorkspaceIndex", EMPTY_INT(), mustBePositive, "Index of the last spectrum to integrate.");
52 declareProperty("IncludePartialBins", false,
53 "If true then partial bins from the beginning and end of the "
54 "input range are also included in the integration.");
55 declareProperty(std::make_unique<ArrayProperty<double>>("RangeLowerList"),
56 "A list of lower integration limits (as X values).");
57 declareProperty(std::make_unique<ArrayProperty<double>>("RangeUpperList"),
58 "A list of upper integration limits (as X values).");
59}
60
67public:
68 bool operator()(const double &left, const double &right) const {
69 // soft equal, if the diff left-right is below a numerical error
70 // (uncertainty) threshold, we cannot say
71 // NOTE this could perhaps use FloatingPointComparison::equal operation
72 return (left < right) && (std::abs(left - right) > 1 * std::numeric_limits<double>::epsilon());
73 }
74};
75
81 // Try and retrieve the optional properties
82
84 double minRange = getProperty("RangeLower");
86 double maxRange = getProperty("RangeUpper");
88 int minWsIndex = getProperty("StartWorkspaceIndex");
90 int maxWsIndex = getProperty("EndWorkspaceIndex");
92 const bool incPartBins = getProperty("IncludePartialBins");
94 const std::vector<double> minRanges = getProperty("RangeLowerList");
96 const std::vector<double> maxRanges = getProperty("RangeUpperList");
97
98 // Get the input workspace
99 MatrixWorkspace_sptr localworkspace = this->getInputWorkspace();
100
101 const auto numberOfSpectra = static_cast<int>(localworkspace->getNumberHistograms());
102
103 // Check 'StartWorkspaceIndex' is in range 0-numberOfSpectra
104 if (minWsIndex >= numberOfSpectra) {
105 g_log.warning("StartWorkspaceIndex out of range! Set to 0.");
106 minWsIndex = 0;
107 }
108 if (isEmpty(maxWsIndex))
109 maxWsIndex = numberOfSpectra - 1;
110 if (maxWsIndex > numberOfSpectra - 1 || maxWsIndex < minWsIndex) {
111 g_log.warning("EndWorkspaceIndex out of range! Set to max workspace index.");
112 maxWsIndex = numberOfSpectra - 1;
113 }
114 auto rangeListCheck = [minWsIndex, maxWsIndex](const std::vector<double> &list, const char *name) {
115 if (!list.empty() && list.size() != static_cast<size_t>(maxWsIndex - minWsIndex) + 1) {
116 std::ostringstream sout;
117 sout << name << " has " << list.size() << " values but it should contain " << maxWsIndex - minWsIndex + 1 << '\n';
118 throw std::runtime_error(sout.str());
119 }
120 };
121 rangeListCheck(minRanges, "RangeLowerList");
122 rangeListCheck(maxRanges, "RangeUpperList");
123
124 double progressStart = 0.0;
125 //---------------------------------------------------------------------------------
126 // Now, determine if the input workspace is actually an EventWorkspace
127 EventWorkspace_sptr eventInputWS = std::dynamic_pointer_cast<EventWorkspace>(localworkspace);
128
129 if (eventInputWS != nullptr) {
130 //------- EventWorkspace as input -------------------------------------
131 if (!minRanges.empty() || !maxRanges.empty()) {
132 throw std::runtime_error("Range lists not supported for EventWorkspaces.");
133 }
134 // Get the eventworkspace rebinned to apply the upper and lowerrange
135 double evntMinRange = isEmpty(minRange) ? eventInputWS->getEventXMin() : minRange;
136 double evntMaxRange = isEmpty(maxRange) ? eventInputWS->getEventXMax() : maxRange;
137 localworkspace = rangeFilterEventWorkspace(eventInputWS, evntMinRange, evntMaxRange);
138
139 progressStart = 0.5;
140 }
141 if (isEmpty(minRange)) {
142 minRange = std::numeric_limits<double>::lowest();
143 }
144
145 // Create the 2D workspace (with 1 bin) for the output
146
147 MatrixWorkspace_sptr outputWorkspace = create<Workspace2D>(*localworkspace, maxWsIndex - minWsIndex + 1, BinEdges(2));
148 auto rebinned_input = std::dynamic_pointer_cast<const RebinnedOutput>(localworkspace);
149 auto rebinned_output = std::dynamic_pointer_cast<RebinnedOutput>(outputWorkspace);
150
151 Progress progress(this, progressStart, 1.0, maxWsIndex - minWsIndex + 1);
152
153 const bool axisIsText = localworkspace->getAxis(1)->isText();
154 const bool axisIsNumeric = localworkspace->getAxis(1)->isNumeric();
155
156 // Loop over spectra
157 PARALLEL_FOR_IF(Kernel::threadSafe(*localworkspace, *outputWorkspace))
158 for (int i = minWsIndex; i <= maxWsIndex; ++i) {
160 // Workspace index on the output
161 const int outWI = i - minWsIndex;
162
163 // Copy Axis values from previous workspace
164 if (axisIsText) {
165 TextAxis *newAxis = dynamic_cast<TextAxis *>(outputWorkspace->getAxis(1));
166 if (newAxis)
167 newAxis->setLabel(outWI, localworkspace->getAxis(1)->label(i));
168 } else if (axisIsNumeric) {
169 NumericAxis *newAxis = dynamic_cast<NumericAxis *>(outputWorkspace->getAxis(1));
170 if (newAxis)
171 newAxis->setValue(outWI, (*(localworkspace->getAxis(1)))(i));
172 }
173
174 // This is the output
175 auto &outSpec = outputWorkspace->getSpectrum(outWI);
176 // This is the input
177 const auto &inSpec = localworkspace->getSpectrum(i);
178
179 // Copy spectrum number, detector IDs
180 outSpec.copyInfoFrom(inSpec);
181
182 const MantidVec *Fin(nullptr);
183 MantidVec *Fout(nullptr);
184 if (rebinned_input)
185 Fin = &rebinned_input->readF(i);
186 if (rebinned_output)
187 Fout = &rebinned_output->dataF(outWI);
188
189 const double lowerLimit = minRanges.empty() ? minRange : std::max(minRange, minRanges[outWI]);
190 const double upperLimit = maxRanges.empty() ? maxRange : std::min(maxRange, maxRanges[outWI]);
191
192 if (upperLimit < lowerLimit) {
193 std::ostringstream sout;
194 sout << "Upper integration limit " << upperLimit << " for workspace index " << i
195 << " smaller than the lower limit " << lowerLimit << ". Setting integral to zero.\n";
196 g_log.warning() << sout.str();
197 progress.report();
198 continue;
199 }
200
201 integrateSpectrum(inSpec, outSpec, Fin, Fout, lowerLimit, upperLimit, incPartBins);
202
203 progress.report();
205 }
207
208 if (rebinned_output) {
209 rebinned_output->finalize(false);
210 }
211
212 // Assign it to the output workspace property
213 setProperty("OutputWorkspace", outputWorkspace);
214}
215
220 const std::vector<double> *Fin, std::vector<double> *Fout, const double lowerLimit,
221 const double upperLimit, const bool incPartBins) {
222 // Retrieve the spectrum into a vector (Histogram)
223 const auto &X = inSpec.x();
224 const auto &Y = inSpec.y();
225 const auto &E = inSpec.e();
226
227 // Find the range [min,max]
228 MantidVec::const_iterator lowit, highit;
229
230 // If doing partial bins, we want to set the bin boundaries to the specified
231 // values regardless of whether they're 'in range' for this spectrum
232 // Have to do this here, ahead of the 'continue' a bit down from here.
233 if (incPartBins) {
234 outSpec.dataX()[0] = lowerLimit;
235 outSpec.dataX()[1] = upperLimit;
236 }
237
238 if (lowerLimit == EMPTY_DBL()) {
239 lowit = X.begin();
240 } else {
241 lowit = std::lower_bound(X.begin(), X.end(), lowerLimit, tolerant_less());
242 }
243
244 if (upperLimit == EMPTY_DBL()) {
245 highit = X.end();
246 } else {
247 highit = std::upper_bound(lowit, X.end(), upperLimit, tolerant_less());
248 }
249
250 // If range specified doesn't overlap with this spectrum then bail out
251 if (lowit == X.end() || highit == X.begin())
252 return;
253
254 // Upper limit is the bin before, i.e. the last value smaller than MaxRange
255 --highit; // (note: decrementing 'end()' is safe for vectors, at least
256 // according to the C++ standard)
257
258 auto distmin = std::distance(X.begin(), lowit);
259 auto distmax = std::distance(X.begin(), highit);
260
261 double sumY = 0.0;
262 double sumE = 0.0;
263 double sumF = 0.0;
264 double Fmin = 0.0;
265 double Fmax = 0.0;
266 double Fnor = 0.0;
267 auto is_distrib = inSpec.yMode() == HistogramData::Histogram::YMode::Frequencies;
268 if (distmax <= distmin) {
269 sumY = 0.;
270 sumE = 0.;
271 } else {
272 if (Fin) {
273 // Workspace has fractional area information, need to take into account
274 sumF = std::accumulate(Fin->cbegin() + distmin, Fin->cbegin() + distmax, 0.0);
275 // Need to normalise by the number of non-NaN bins - see issue #33407 for details
276 Fnor = static_cast<double>(
277 std::count_if(Fin->begin() + distmin, Fin->begin() + distmax, [](double f) { return f != 0.; }));
278 if (distmin > 0)
279 Fmin = (*Fin)[distmin - 1];
280 Fmax = (*Fin)[static_cast<std::size_t>(distmax) < Fin->size() ? distmax : Fin->size() - 1];
281 }
282 if (!is_distrib) {
283 // Sum the Y, and sum the E in quadrature
284 {
285 sumY = std::accumulate(Y.cbegin() + distmin, Y.cbegin() + distmax, 0.0);
286 sumE = std::accumulate(E.cbegin() + distmin, E.cbegin() + distmax, 0.0, VectorHelper::SumSquares<double>());
287 }
288 } else {
289 // Sum Y*binwidth and Sum the (E*binwidth)^2.
290 std::vector<double> widths(X.size());
291 // highit+1 is safe while input workspace guaranteed to be histogram
292 std::adjacent_difference(lowit, highit + 1, widths.begin());
293 sumY = std::inner_product(Y.begin() + distmin, Y.begin() + distmax, widths.begin() + 1, 0.0);
294 sumE = std::inner_product(E.begin() + distmin, E.begin() + distmax, widths.begin() + 1, 0.0, std::plus<double>(),
296 }
297 }
298 // If partial bins are included, set integration range to exact range
299 // given and add on contributions from partial bins either side of range.
300 if (incPartBins) {
301 if ((distmax <= distmin) && (distmin > 0) && (highit < X.end() - 1)) {
302 // handle special case where lower and upper limit are in the same bin
303 const double lower_bin = *lowit;
304 const double prev_bin = *(lowit - 1);
305 double fraction = (upperLimit - lowerLimit);
306 if (!is_distrib) {
307 fraction /= (lower_bin - prev_bin);
308 }
309 const MantidVec::size_type val_index = distmin - 1;
310 sumY += Y[val_index] * fraction;
311 const double eval = E[val_index];
312 sumE += eval * eval * fraction * fraction;
313 if (Fin) {
314 sumF += Fmin * fraction;
315 if (Fmin != 0.0)
316 Fnor += fraction;
317 }
318 } else {
319 if (distmin > 0) {
320 const double lower_bin = *lowit;
321 const double prev_bin = *(lowit - 1);
322 double fraction = (lower_bin - lowerLimit);
323 if (!is_distrib) {
324 fraction /= (lower_bin - prev_bin);
325 }
326 const MantidVec::size_type val_index = distmin - 1;
327 sumY += Y[val_index] * fraction;
328 const double eval = E[val_index];
329 sumE += eval * eval * fraction * fraction;
330 if (Fin) {
331 sumF += Fmin * fraction;
332 if (Fmin != 0.0)
333 Fnor += fraction;
334 }
335 }
336 if (highit < X.end() - 1) {
337 const double upper_bin = *highit;
338 const double next_bin = *(highit + 1);
339 double fraction = (upperLimit - upper_bin);
340 if (!is_distrib) {
341 fraction /= (next_bin - upper_bin);
342 }
343 sumY += Y[distmax] * fraction;
344 const double eval = E[distmax];
345 sumE += eval * eval * fraction * fraction;
346 if (Fin) {
347 sumF += Fmax * fraction;
348 if (Fmax != 0.0)
349 Fnor += fraction;
350 }
351 }
352 }
353 } else {
354 outSpec.mutableX()[0] = lowit == X.end() ? *(lowit - 1) : *(lowit);
355 outSpec.mutableX()[1] = *highit;
356 }
357
358 outSpec.mutableY()[0] = sumY;
359 outSpec.mutableE()[0] = sqrt(sumE); // Propagate Gaussian error
360
361 if (Fout) {
362 (*Fout)[0] = sumF;
363 if (Fnor != 0)
364 (*Fout)[0] /= Fnor;
365 }
366}
367
372 double minRange, double maxRange) {
373 bool childLog = g_log.is(Logger::Priority::PRIO_DEBUG);
374 auto childAlg = createChildAlgorithm("Rebin", 0, 0.5, childLog);
375 childAlg->setProperty("InputWorkspace", workspace);
376 std::ostringstream binParams;
377 binParams << minRange << "," << maxRange - minRange << "," << maxRange;
378 childAlg->setPropertyValue("Params", binParams.str());
379 childAlg->setProperty("PreserveEvents", false);
380 childAlg->executeAsChildAlg();
381 return childAlg->getProperty("OutputWorkspace");
382}
383
391 MatrixWorkspace_sptr temp = getProperty("InputWorkspace");
392
393 if (temp->id() == "RebinnedOutput") {
394 // Clean the input workspace in the RebinnedOutput case for nan's and
395 // inf's in order to treat the data correctly later.
396 auto alg = createChildAlgorithm("ReplaceSpecialValues");
397 alg->setProperty<MatrixWorkspace_sptr>("InputWorkspace", temp);
398 std::string outName = "_" + temp->getName() + "_clean";
399 alg->setProperty("OutputWorkspace", outName);
400 alg->setProperty("NaNValue", 0.0);
401 alg->setProperty("NaNError", 0.0);
402 alg->setProperty("InfinityValue", 0.0);
403 alg->setProperty("InfinityError", 0.0);
404 alg->executeAsChildAlg();
405 temp = alg->getProperty("OutputWorkspace");
406 // Now if the workspace is "finalized" need to undo this before integrating
407 std::dynamic_pointer_cast<RebinnedOutput>(temp)->unfinalize();
408 }
409
410 // To integrate point data it will be converted to histograms
411 if (!temp->isHistogramData()) {
412 auto alg = this->createChildAlgorithm("ConvertToHistogram");
413 alg->setProperty<MatrixWorkspace_sptr>("InputWorkspace", temp);
414 std::string outName = "_" + temp->getName() + "_histogram";
415 alg->setProperty("OutputWorkspace", outName);
416 alg->executeAsChildAlg();
417 temp = alg->getProperty("OutputWorkspace");
418 temp->setDistribution(true);
419 }
420
421 return temp;
422}
423
424std::map<std::string, std::string> Integration::validateInputs() {
425 std::map<std::string, std::string> issues;
426 const double minRange = getProperty("RangeLower");
427 const double maxRange = getProperty("RangeUpper");
428 const std::vector<double> minRanges = getProperty("RangeLowerList");
429 const std::vector<double> maxRanges = getProperty("RangeUpperList");
430 if (!minRanges.empty() && !maxRanges.empty() && minRanges.size() != maxRanges.size()) {
431 issues["RangeLowerList"] = "RangeLowerList has different number of values as RangeUpperList.";
432 return issues;
433 }
434 for (size_t i = 0; i < minRanges.size(); ++i) {
435 const auto x = minRanges[i];
436 if (!isEmpty(maxRange) && x > maxRange) {
437 issues["RangeLowerList"] = "RangeLowerList has a value greater than RangeUpper.";
438 break;
439 } else if (!maxRanges.empty() && x > maxRanges[i]) {
440 issues["RangeLowerList"] = "RangeLowerList has a value greater than the "
441 "corresponding one in RangeUpperList.";
442 break;
443 }
444 }
445 if (!isEmpty(minRange)) {
446 if (std::any_of(maxRanges.cbegin(), maxRanges.cend(), [minRange](const auto x) { return x < minRange; })) {
447 issues["RangeUpperList"] = "RangeUpperList has a value lower than RangeLower.";
448 }
449 }
450 return issues;
451}
452
453} // namespace Mantid::Algorithms
#define DECLARE_ALGORITHM(classname)
Definition Algorithm.h:538
IPeaksWorkspace_sptr workspace
double left
double right
#define PARALLEL_START_INTERRUPT_REGION
Begins a block to skip processing is the algorithm has been interupted Note the end of the block if n...
#define PARALLEL_END_INTERRUPT_REGION
Ends a block to skip processing is the algorithm has been interupted Note the start of the block if n...
#define PARALLEL_FOR_IF(condition)
Empty definitions - to enable set your complier to enable openMP.
#define PARALLEL_CHECK_INTERRUPT_REGION
Adds a check after a Parallel region to see if it was interupted.
void declareProperty(std::unique_ptr< Kernel::Property > p, const std::string &doc="") override
Add a property to the list of managed properties.
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.
static bool isEmpty(const NumT toCheck)
checks that the value was not set by users, uses the value in empty double/int.
A "spectrum" is an object that holds the data for a particular spectrum, in particular:
Definition ISpectrum.h:38
HistogramData::Histogram::YMode yMode() const
Definition ISpectrum.h:104
HistogramData::HistogramY & mutableY() &
Definition ISpectrum.h:177
const HistogramData::HistogramX & x() const
Definition ISpectrum.h:171
virtual const HistogramData::HistogramE & e() const
Definition ISpectrum.h:173
virtual const HistogramData::HistogramY & y() const
Definition ISpectrum.h:172
HistogramData::HistogramX & mutableX() &
Definition ISpectrum.h:175
HistogramData::HistogramE & mutableE() &
Definition ISpectrum.h:181
virtual MantidVec & dataX()=0
Class to represent a numeric axis of a workspace.
Definition NumericAxis.h:29
void setValue(const std::size_t &index, const double &value) override
Set the value at a specific index.
Helper class for reporting progress from algorithms.
Definition Progress.h:25
Class to represent a text axis of a workspace.
Definition TextAxis.h:36
void setLabel(const std::size_t &index, const std::string &lbl)
Set the label at the given index.
Definition TextAxis.cpp:114
A property class for workspaces.
std::map< std::string, std::string > validateInputs() override
Method checking errors on ALL the inputs, before execution.
void init() override
Initialisation method.
void integrateSpectrum(const API::ISpectrum &inSpec, API::ISpectrum &outSpec, const std::vector< double > *Fin, std::vector< double > *Fout, double lowerLimit, double upperLimit, bool incPartBins)
Integrate a single spectrum between the supplied limits.
API::MatrixWorkspace_sptr getInputWorkspace()
Get the input workspace.
void exec() override
Executes the algorithm.
API::MatrixWorkspace_sptr rangeFilterEventWorkspace(const API::MatrixWorkspace_sptr &workspace, double minRange, double maxRange)
Uses rebin to reduce event workspaces to a single bin histogram.
const std::string name() const override
Algorithm's name for identification overriding a virtual method.
Definition Integration.h:46
Support for a property that holds an array of values.
IPropertyManager * setProperty(const std::string &name, const T &value)
Templated method to set the value of a PropertyWithValue.
void warning(const std::string &msg)
Logs at warning level.
Definition Logger.cpp:117
bool is(int level) const
Returns true if at least the given log level is set.
Definition Logger.cpp:177
std::shared_ptr< MatrixWorkspace > MatrixWorkspace_sptr
shared pointer to the matrix workspace base class
std::shared_ptr< EventWorkspace > EventWorkspace_sptr
shared pointer to the EventWorkspace class
std::enable_if< std::is_pointer< Arg >::value, bool >::type threadSafe(Arg workspace)
Thread-safety check Checks the workspace to ensure it is suitable for multithreaded access.
constexpr int EMPTY_INT() noexcept
Returns what we consider an "empty" integer within a property.
Definition EmptyValues.h:24
std::vector< double > MantidVec
typedef for the data storage used in Mantid matrix workspaces
Definition cow_ptr.h:172
constexpr double EMPTY_DBL() noexcept
Returns what we consider an "empty" double within a property.
Definition EmptyValues.h:42
Std-style comparision function object (satisfies the requirements of Compare)
bool operator()(const double &left, const double &right) const
@ Input
An input workspace.
Definition Property.h:53
@ Output
An output workspace.
Definition Property.h:54
Functor to accumulate a sum of squares.
Functor giving the product of the squares of the arguments.