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 return (left < right) && (std::abs(left - right) > 1 * std::numeric_limits<double>::epsilon());
72 }
73};
74
80 // Try and retrieve the optional properties
81
83 double minRange = getProperty("RangeLower");
85 double maxRange = getProperty("RangeUpper");
87 int minWsIndex = getProperty("StartWorkspaceIndex");
89 int maxWsIndex = getProperty("EndWorkspaceIndex");
91 const bool incPartBins = getProperty("IncludePartialBins");
93 const std::vector<double> minRanges = getProperty("RangeLowerList");
95 const std::vector<double> maxRanges = getProperty("RangeUpperList");
96
97 // Get the input workspace
98 MatrixWorkspace_sptr localworkspace = this->getInputWorkspace();
99
100 const auto numberOfSpectra = static_cast<int>(localworkspace->getNumberHistograms());
101
102 // Check 'StartWorkspaceIndex' is in range 0-numberOfSpectra
103 if (minWsIndex >= numberOfSpectra) {
104 g_log.warning("StartWorkspaceIndex out of range! Set to 0.");
105 minWsIndex = 0;
106 }
107 if (isEmpty(maxWsIndex))
108 maxWsIndex = numberOfSpectra - 1;
109 if (maxWsIndex > numberOfSpectra - 1 || maxWsIndex < minWsIndex) {
110 g_log.warning("EndWorkspaceIndex out of range! Set to max workspace index.");
111 maxWsIndex = numberOfSpectra - 1;
112 }
113 auto rangeListCheck = [minWsIndex, maxWsIndex](const std::vector<double> &list, const char *name) {
114 if (!list.empty() && list.size() != static_cast<size_t>(maxWsIndex - minWsIndex) + 1) {
115 std::ostringstream sout;
116 sout << name << " has " << list.size() << " values but it should contain " << maxWsIndex - minWsIndex + 1 << '\n';
117 throw std::runtime_error(sout.str());
118 }
119 };
120 rangeListCheck(minRanges, "RangeLowerList");
121 rangeListCheck(maxRanges, "RangeUpperList");
122
123 double progressStart = 0.0;
124 //---------------------------------------------------------------------------------
125 // Now, determine if the input workspace is actually an EventWorkspace
126 EventWorkspace_sptr eventInputWS = std::dynamic_pointer_cast<EventWorkspace>(localworkspace);
127
128 if (eventInputWS != nullptr) {
129 //------- EventWorkspace as input -------------------------------------
130 if (!minRanges.empty() || !maxRanges.empty()) {
131 throw std::runtime_error("Range lists not supported for EventWorkspaces.");
132 }
133 // Get the eventworkspace rebinned to apply the upper and lowerrange
134 double evntMinRange = isEmpty(minRange) ? eventInputWS->getEventXMin() : minRange;
135 double evntMaxRange = isEmpty(maxRange) ? eventInputWS->getEventXMax() : maxRange;
136 localworkspace = rangeFilterEventWorkspace(eventInputWS, evntMinRange, evntMaxRange);
137
138 progressStart = 0.5;
139 }
140 if (isEmpty(minRange)) {
141 minRange = std::numeric_limits<double>::lowest();
142 }
143
144 // Create the 2D workspace (with 1 bin) for the output
145
146 MatrixWorkspace_sptr outputWorkspace = create<Workspace2D>(*localworkspace, maxWsIndex - minWsIndex + 1, BinEdges(2));
147 auto rebinned_input = std::dynamic_pointer_cast<const RebinnedOutput>(localworkspace);
148 auto rebinned_output = std::dynamic_pointer_cast<RebinnedOutput>(outputWorkspace);
149
150 Progress progress(this, progressStart, 1.0, maxWsIndex - minWsIndex + 1);
151
152 const bool axisIsText = localworkspace->getAxis(1)->isText();
153 const bool axisIsNumeric = localworkspace->getAxis(1)->isNumeric();
154
155 // Loop over spectra
156 PARALLEL_FOR_IF(Kernel::threadSafe(*localworkspace, *outputWorkspace))
157 for (int i = minWsIndex; i <= maxWsIndex; ++i) {
159 // Workspace index on the output
160 const int outWI = i - minWsIndex;
161
162 // Copy Axis values from previous workspace
163 if (axisIsText) {
164 TextAxis *newAxis = dynamic_cast<TextAxis *>(outputWorkspace->getAxis(1));
165 if (newAxis)
166 newAxis->setLabel(outWI, localworkspace->getAxis(1)->label(i));
167 } else if (axisIsNumeric) {
168 NumericAxis *newAxis = dynamic_cast<NumericAxis *>(outputWorkspace->getAxis(1));
169 if (newAxis)
170 newAxis->setValue(outWI, (*(localworkspace->getAxis(1)))(i));
171 }
172
173 // This is the output
174 auto &outSpec = outputWorkspace->getSpectrum(outWI);
175 // This is the input
176 const auto &inSpec = localworkspace->getSpectrum(i);
177
178 // Copy spectrum number, detector IDs
179 outSpec.copyInfoFrom(inSpec);
180
181 const MantidVec *Fin(nullptr);
182 MantidVec *Fout(nullptr);
183 if (rebinned_input)
184 Fin = &rebinned_input->readF(i);
185 if (rebinned_output)
186 Fout = &rebinned_output->dataF(outWI);
187
188 const double lowerLimit = minRanges.empty() ? minRange : std::max(minRange, minRanges[outWI]);
189 const double upperLimit = maxRanges.empty() ? maxRange : std::min(maxRange, maxRanges[outWI]);
190
191 if (upperLimit < lowerLimit) {
192 std::ostringstream sout;
193 sout << "Upper integration limit " << upperLimit << " for workspace index " << i
194 << " smaller than the lower limit " << lowerLimit << ". Setting integral to zero.\n";
195 g_log.warning() << sout.str();
196 progress.report();
197 continue;
198 }
199
200 integrateSpectrum(inSpec, outSpec, Fin, Fout, lowerLimit, upperLimit, incPartBins);
201
202 progress.report();
204 }
206
207 if (rebinned_output) {
208 rebinned_output->finalize(false);
209 }
210
211 // Assign it to the output workspace property
212 setProperty("OutputWorkspace", outputWorkspace);
213}
214
219 const std::vector<double> *Fin, std::vector<double> *Fout, const double lowerLimit,
220 const double upperLimit, const bool incPartBins) {
221 // Retrieve the spectrum into a vector (Histogram)
222 const auto &X = inSpec.x();
223 const auto &Y = inSpec.y();
224 const auto &E = inSpec.e();
225
226 // Find the range [min,max]
227 MantidVec::const_iterator lowit, highit;
228
229 // If doing partial bins, we want to set the bin boundaries to the specified
230 // values regardless of whether they're 'in range' for this spectrum
231 // Have to do this here, ahead of the 'continue' a bit down from here.
232 if (incPartBins) {
233 outSpec.dataX()[0] = lowerLimit;
234 outSpec.dataX()[1] = upperLimit;
235 }
236
237 if (lowerLimit == EMPTY_DBL()) {
238 lowit = X.begin();
239 } else {
240 lowit = std::lower_bound(X.begin(), X.end(), lowerLimit, tolerant_less());
241 }
242
243 if (upperLimit == EMPTY_DBL()) {
244 highit = X.end();
245 } else {
246 highit = std::upper_bound(lowit, X.end(), upperLimit, tolerant_less());
247 }
248
249 // If range specified doesn't overlap with this spectrum then bail out
250 if (lowit == X.end() || highit == X.begin())
251 return;
252
253 // Upper limit is the bin before, i.e. the last value smaller than MaxRange
254 --highit; // (note: decrementing 'end()' is safe for vectors, at least
255 // according to the C++ standard)
256
257 auto distmin = std::distance(X.begin(), lowit);
258 auto distmax = std::distance(X.begin(), highit);
259
260 double sumY = 0.0;
261 double sumE = 0.0;
262 double sumF = 0.0;
263 double Fmin = 0.0;
264 double Fmax = 0.0;
265 double Fnor = 0.0;
266 auto is_distrib = inSpec.yMode() == HistogramData::Histogram::YMode::Frequencies;
267 if (distmax <= distmin) {
268 sumY = 0.;
269 sumE = 0.;
270 } else {
271 if (Fin) {
272 // Workspace has fractional area information, need to take into account
273 sumF = std::accumulate(Fin->begin() + distmin, Fin->begin() + distmax, 0.0);
274 // Need to normalise by the number of non-NaN bins - see issue #33407 for details
275 Fnor = static_cast<double>(
276 std::count_if(Fin->begin() + distmin, Fin->begin() + distmax, [](double f) { return f != 0.; }));
277 if (distmin > 0)
278 Fmin = (*Fin)[distmin - 1];
279 Fmax = (*Fin)[static_cast<std::size_t>(distmax) < Fin->size() ? distmax : Fin->size() - 1];
280 }
281 if (!is_distrib) {
282 // Sum the Y, and sum the E in quadrature
283 {
284 sumY = std::accumulate(Y.begin() + distmin, Y.begin() + distmax, 0.0);
285 sumE = std::accumulate(E.begin() + distmin, E.begin() + distmax, 0.0, VectorHelper::SumSquares<double>());
286 }
287 } else {
288 // Sum Y*binwidth and Sum the (E*binwidth)^2.
289 std::vector<double> widths(X.size());
290 // highit+1 is safe while input workspace guaranteed to be histogram
291 std::adjacent_difference(lowit, highit + 1, widths.begin());
292 sumY = std::inner_product(Y.begin() + distmin, Y.begin() + distmax, widths.begin() + 1, 0.0);
293 sumE = std::inner_product(E.begin() + distmin, E.begin() + distmax, widths.begin() + 1, 0.0, std::plus<double>(),
295 }
296 }
297 // If partial bins are included, set integration range to exact range
298 // given and add on contributions from partial bins either side of range.
299 if (incPartBins) {
300 if ((distmax <= distmin) && (distmin > 0) && (highit < X.end() - 1)) {
301 // handle special case where lower and upper limit are in the same bin
302 const double lower_bin = *lowit;
303 const double prev_bin = *(lowit - 1);
304 double fraction = (upperLimit - lowerLimit);
305 if (!is_distrib) {
306 fraction /= (lower_bin - prev_bin);
307 }
308 const MantidVec::size_type val_index = distmin - 1;
309 sumY += Y[val_index] * fraction;
310 const double eval = E[val_index];
311 sumE += eval * eval * fraction * fraction;
312 if (Fin) {
313 sumF += Fmin * fraction;
314 if (Fmin != 0.0)
315 Fnor += fraction;
316 }
317 } else {
318 if (distmin > 0) {
319 const double lower_bin = *lowit;
320 const double prev_bin = *(lowit - 1);
321 double fraction = (lower_bin - lowerLimit);
322 if (!is_distrib) {
323 fraction /= (lower_bin - prev_bin);
324 }
325 const MantidVec::size_type val_index = distmin - 1;
326 sumY += Y[val_index] * fraction;
327 const double eval = E[val_index];
328 sumE += eval * eval * fraction * fraction;
329 if (Fin) {
330 sumF += Fmin * fraction;
331 if (Fmin != 0.0)
332 Fnor += fraction;
333 }
334 }
335 if (highit < X.end() - 1) {
336 const double upper_bin = *highit;
337 const double next_bin = *(highit + 1);
338 double fraction = (upperLimit - upper_bin);
339 if (!is_distrib) {
340 fraction /= (next_bin - upper_bin);
341 }
342 sumY += Y[distmax] * fraction;
343 const double eval = E[distmax];
344 sumE += eval * eval * fraction * fraction;
345 if (Fin) {
346 sumF += Fmax * fraction;
347 if (Fmax != 0.0)
348 Fnor += fraction;
349 }
350 }
351 }
352 } else {
353 outSpec.mutableX()[0] = lowit == X.end() ? *(lowit - 1) : *(lowit);
354 outSpec.mutableX()[1] = *highit;
355 }
356
357 outSpec.mutableY()[0] = sumY;
358 outSpec.mutableE()[0] = sqrt(sumE); // Propagate Gaussian error
359 if (Fout) {
360 (*Fout)[0] = sumF / Fnor;
361 }
362}
363
368 double minRange, double maxRange) {
369 bool childLog = g_log.is(Logger::Priority::PRIO_DEBUG);
370 auto childAlg = createChildAlgorithm("Rebin", 0, 0.5, childLog);
371 childAlg->setProperty("InputWorkspace", workspace);
372 std::ostringstream binParams;
373 binParams << minRange << "," << maxRange - minRange << "," << maxRange;
374 childAlg->setPropertyValue("Params", binParams.str());
375 childAlg->setProperty("PreserveEvents", false);
376 childAlg->executeAsChildAlg();
377 return childAlg->getProperty("OutputWorkspace");
378}
379
387 MatrixWorkspace_sptr temp = getProperty("InputWorkspace");
388
389 if (temp->id() == "RebinnedOutput") {
390 // Clean the input workspace in the RebinnedOutput case for nan's and
391 // inf's in order to treat the data correctly later.
392 auto alg = createChildAlgorithm("ReplaceSpecialValues");
393 alg->setProperty<MatrixWorkspace_sptr>("InputWorkspace", temp);
394 std::string outName = "_" + temp->getName() + "_clean";
395 alg->setProperty("OutputWorkspace", outName);
396 alg->setProperty("NaNValue", 0.0);
397 alg->setProperty("NaNError", 0.0);
398 alg->setProperty("InfinityValue", 0.0);
399 alg->setProperty("InfinityError", 0.0);
400 alg->executeAsChildAlg();
401 temp = alg->getProperty("OutputWorkspace");
402 // Now if the workspace is "finalized" need to undo this before integrating
403 std::dynamic_pointer_cast<RebinnedOutput>(temp)->unfinalize();
404 }
405
406 // To integrate point data it will be converted to histograms
407 if (!temp->isHistogramData()) {
408 auto alg = this->createChildAlgorithm("ConvertToHistogram");
409 alg->setProperty<MatrixWorkspace_sptr>("InputWorkspace", temp);
410 std::string outName = "_" + temp->getName() + "_histogram";
411 alg->setProperty("OutputWorkspace", outName);
412 alg->executeAsChildAlg();
413 temp = alg->getProperty("OutputWorkspace");
414 temp->setDistribution(true);
415 }
416
417 return temp;
418}
419
420std::map<std::string, std::string> Integration::validateInputs() {
421 std::map<std::string, std::string> issues;
422 const double minRange = getProperty("RangeLower");
423 const double maxRange = getProperty("RangeUpper");
424 const std::vector<double> minRanges = getProperty("RangeLowerList");
425 const std::vector<double> maxRanges = getProperty("RangeUpperList");
426 if (!minRanges.empty() && !maxRanges.empty() && minRanges.size() != maxRanges.size()) {
427 issues["RangeLowerList"] = "RangeLowerList has different number of values as RangeUpperList.";
428 return issues;
429 }
430 for (size_t i = 0; i < minRanges.size(); ++i) {
431 const auto x = minRanges[i];
432 if (!isEmpty(maxRange) && x > maxRange) {
433 issues["RangeLowerList"] = "RangeLowerList has a value greater than RangeUpper.";
434 break;
435 } else if (!maxRanges.empty() && x > maxRanges[i]) {
436 issues["RangeLowerList"] = "RangeLowerList has a value greater than the "
437 "corresponding one in RangeUpperList.";
438 break;
439 }
440 }
441 if (!isEmpty(minRange)) {
442 if (std::any_of(maxRanges.cbegin(), maxRanges.cend(), [minRange](const auto x) { return x < minRange; })) {
443 issues["RangeUpperList"] = "RangeUpperList has a value lower than RangeLower.";
444 }
445 }
446 return issues;
447}
448
449} // namespace Mantid::Algorithms
#define DECLARE_ALGORITHM(classname)
Definition: Algorithm.h:576
IPeaksWorkspace_sptr workspace
Definition: IndexPeaks.cpp:114
double left
Definition: LineProfile.cpp:80
double right
Definition: LineProfile.cpp:81
#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...
Definition: MultiThreaded.h:94
#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.
Definition: Algorithm.cpp:1913
TypedValue getProperty(const std::string &name) const override
Get the value of a property.
Definition: Algorithm.cpp:2076
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.
Definition: Algorithm.cpp:842
Kernel::Logger & g_log
Definition: Algorithm.h:451
void progress(double p, const std::string &msg="", double estimatedTime=0.0, int progressPrecision=0)
Sends ProgressNotification.
Definition: Algorithm.cpp:231
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:39
HistogramData::Histogram::YMode yMode() const
Definition: ISpectrum.h:105
HistogramData::HistogramY & mutableY() &
Definition: ISpectrum.h:178
const HistogramData::HistogramX & x() const
Definition: ISpectrum.h:172
virtual const HistogramData::HistogramE & e() const
Definition: ISpectrum.h:174
virtual const HistogramData::HistogramY & y() const
Definition: ISpectrum.h:173
HistogramData::HistogramX & mutableX() &
Definition: ISpectrum.h:176
HistogramData::HistogramE & mutableE() &
Definition: ISpectrum.h:182
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:104
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.
Definition: Integration.cpp:39
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.
Definition: Integration.cpp:79
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.
Definition: ArrayProperty.h:28
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:86
bool is(int level) const
Returns true if at least the given log level is set.
Definition: Logger.cpp:146
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.
Definition: MultiThreaded.h:22
constexpr int EMPTY_INT() noexcept
Returns what we consider an "empty" integer within a property.
Definition: EmptyValues.h:25
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:43
Std-style comparision function object (satisfies the requirements of Compare)
Definition: Integration.cpp:66
bool operator()(const double &left, const double &right) const
Definition: Integration.cpp:68
@ Input
An input workspace.
Definition: Property.h:53
@ Output
An output workspace.
Definition: Property.h:54
Functor to accumulate a sum of squares.
Definition: VectorHelper.h:170
Functor giving the product of the squares of the arguments.
Definition: VectorHelper.h:177