Mantid
Loading...
Searching...
No Matches
InterpolatingRebin.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 +
8#include "MantidAPI/Axis.h"
13#include "MantidHistogramData/Histogram.h"
16#include "MantidKernel/Spline.h"
18
19#include <gsl/gsl_errno.h>
20
21#include <boost/lexical_cast.hpp>
22#include <span>
23
24namespace Mantid::Algorithms {
25
26// Register the class into the algorithm factory
27DECLARE_ALGORITHM(InterpolatingRebin)
28
29using namespace Kernel;
30using namespace API;
31using namespace HistogramData;
32using namespace DataObjects;
33
38 declareProperty(std::make_unique<WorkspaceProperty<>>("InputWorkspace", "", Direction::Input),
39 "Workspace containing the input data");
40 declareProperty(std::make_unique<WorkspaceProperty<>>("OutputWorkspace", "", Direction::Output),
41 "The name to give the output workspace");
42
43 declareProperty(std::make_unique<ArrayProperty<double>>("Params", std::make_shared<RebinParamsValidator>()),
44 "A comma separated list of first bin boundary, width, last bin boundary. "
45 "Optionally "
46 "this can be followed by a comma and more widths and last boundary "
47 "pairs. "
48 "Optionally this can also be a single number, which is the bin width. "
49 "In this case, the boundary of binning will be determined by minimum and "
50 "maximum TOF "
51 "values among all events, or previous binning boundary, in case of event "
52 "Workspace, or "
53 "non-event Workspace, respectively. Negative width values indicate "
54 "logarithmic binning. ");
55}
56
62std::map<std::string, std::string> InterpolatingRebin::validateInputs() {
63 std::map<std::string, std::string> helpMessages;
64
65 // as part of validation, force the binwidth to be compatible with set bin mode
66 MatrixWorkspace_sptr inputWS = getProperty("InputWorkspace");
67 std::vector<double> rbParams = getProperty("Params");
68 try {
69 Rebin::rebinParamsFromInput(rbParams, *inputWS, g_log);
70 } catch (std::exception &err) {
71 helpMessages["Params"] = err.what();
72 }
73 return helpMessages;
74}
75
81 // Get the input workspace
82 MatrixWorkspace_sptr inputW = getProperty("InputWorkspace");
83
84 // retrieve the properties
85 std::vector<double> rb_params = Rebin::rebinParamsFromInput(getProperty("Params"), *inputW, g_log);
86 // create new output X axis
87 std::vector<double> xAxisTmp;
88 const std::size_t ntcnew = VectorHelper::createAxisFromRebinParams(rb_params, xAxisTmp);
89 HistogramData::BinEdges XValues_new(std::move(xAxisTmp));
90
91 const auto nHists = static_cast<int>(inputW->getNumberHistograms());
92 // make output Workspace the same type as the input but with the new axes
93 MatrixWorkspace_sptr outputW = create<MatrixWorkspace>(*inputW, BinEdges(ntcnew));
94 // Copy over the 'vertical' axis
95 if (inputW->axes() > 1)
96 outputW->replaceAxis(1, std::unique_ptr<Axis>(inputW->getAxis(1)->clone(outputW.get())));
97 outputW->setDistribution(true);
98
99 // this calculation requires a distribution workspace but deal with the
100 // situation when we don't get this
101 bool distCon(false);
102 if (!inputW->isDistribution()) {
103 g_log.debug() << "Converting the input workspace to a distribution\n";
105 distCon = true;
106 }
107
108 try {
109 // evaluate the rebinned data
110 outputYandEValues(inputW, XValues_new, outputW);
111 } catch (std::exception &) {
112
113 if (distCon) {
114 // we need to return the input workspace to the state we found it in
116 }
117 throw;
118 }
119
120 // check if there was a convert to distribution done previously
121 if (distCon) {
122 g_log.debug() << "Converting the input and output workspaces _from_ distributions\n";
124 // the calculation produces a distribution workspace but if they passed a
125 // non-distribution workspace they maybe not expect it, so convert back to
126 // the same form that was given
128 outputW->setDistribution(false);
129 }
130
131 // Now propagate any masking correctly to the output workspace
132 // More efficient to have this in a separate loop because
133 // MatrixWorkspace::maskBins blocks multi-threading
134 for (int i = 0; i < nHists; ++i) {
135 if (inputW->hasMaskedBins(i)) // Does the current spectrum have any masked bins?
136 {
137 this->propagateMasks(inputW, outputW, i);
138 }
139 }
140
141 for (int i = 0; i < outputW->axes(); ++i) {
142 outputW->getAxis(i)->unit() = inputW->getAxis(i)->unit();
143 }
144
145 // Assign it to the output workspace property
146 setProperty("OutputWorkspace", outputW);
147}
155 const HistogramData::BinEdges &XValues_new,
156 const API::MatrixWorkspace_sptr &outputW) {
157 g_log.debug() << "Preparing to calculate y-values using splines and estimate errors\n";
158
159 // prepare to use GSL functions but don't let them terminate Mantid
160 gsl_error_handler_t *old_handler = gsl_set_error_handler(nullptr);
161
162 const auto histnumber = static_cast<int>(inputW->getNumberHistograms());
163 Progress prog(this, 0.0, 1.0, histnumber);
164 for (int hist = 0; hist < histnumber; ++hist) {
165
166 try {
167 // output data arrays are implicitly filled by function
168 outputW->setHistogram(hist, cubicInterpolation(inputW->histogram(hist), XValues_new));
169 } catch (std::exception &ex) {
170 g_log.error() << "Error in rebin function: " << ex.what() << '\n';
171 throw;
172 }
173
174 // Populate the output workspace X values
175 outputW->setBinEdges(hist, XValues_new);
176
177 prog.report();
178 }
179
180 gsl_set_error_handler(old_handler);
181}
182
209Histogram InterpolatingRebin::cubicInterpolation(const Histogram &oldHistogram, const BinEdges &xNew) const {
210 const auto &yOld = oldHistogram.y();
211
212 const size_t size_old = yOld.size();
213 if (size_old == 0)
214 throw std::runtime_error("Empty spectrum found, aborting!");
215
216 const size_t size_new = xNew.size() - 1; // -1 because BinEdges
217
218 // get the bin centres of the input data
219 auto xCensOld = oldHistogram.points();
220 VectorHelper::convertToBinCentre(oldHistogram.x().rawData(), xCensOld.mutableRawData());
221 // the centres of the output data
222 Points xCensNew(size_new);
223 VectorHelper::convertToBinCentre(xNew.rawData(), xCensNew.mutableRawData());
224
225 // find the range of input values whose x-values just suround the output
226 // x-values
227 size_t oldIn1 = std::lower_bound(xCensOld.begin(), xCensOld.end(), xCensNew.front()) - xCensOld.begin();
228 if (oldIn1 == 0) { // the lowest interpolation value might be out of range but
229 // if it is almost on the boundary let it through
230 if (std::abs(xCensOld.front() - xCensNew.front()) < 1e-8 * (xCensOld.back() - xCensOld.front())) {
231 oldIn1 = 1;
232 // make what should be a very small correction
233 xCensNew.mutableRawData().front() = xCensOld.front();
234 }
235 }
236
237 size_t oldIn2 = std::lower_bound(xCensOld.begin(), xCensOld.end(), xCensNew.back()) - xCensOld.begin();
238 if (oldIn2 == size_old) { // the highest point is nearly out of range of the
239 // input data but if it's very near the boundary let
240 // it through
241 if (std::abs(xCensOld.back() - xCensNew.back()) < 1e-8 * (xCensOld.back() - xCensOld.front())) {
242 oldIn2 = size_old - 1;
243 // make what should be a very small correction
244 xCensNew.mutableRawData().back() = xCensOld.back();
245 }
246 }
247
248 // check that the intepolation points fit well enough within the data for
249 // reliable intepolation to be done
250 bool goodRangeLow(false), goodRangeHigh(false), canInterpol(false);
251 if (oldIn1 > 1) { // set the range of the fit, including more input data to
252 // improve accuracy
253 oldIn1 -= 2;
254 goodRangeLow = true;
255 canInterpol = true;
256 } else {
257 if (oldIn1 > 0) {
258 canInterpol = true;
259 oldIn1--;
260 }
261 }
262
263 if (oldIn2 < size_old - 1) {
264 oldIn2++;
265 goodRangeHigh = true;
266 } else {
267 if (oldIn2 >= size_old) {
268 canInterpol = false;
269 }
270 }
271
272 const auto &xOld = oldHistogram.x();
273 const auto &eOld = oldHistogram.e();
274
275 // No Interpolation branch
276 if (!canInterpol) {
277 if (VectorHelper::isConstantValue(yOld.rawData())) {
278 // this copies the single y-value into the output array, errors are still
279 // calculated from the nearest input data points
280 // this is as much as we need to do in this (trival) case
281 return noInterpolation(oldHistogram, xNew);
282 } else { // some points are two close to the edge of the data
283
284 throw std::invalid_argument(std::string("At least one x-value to interpolate to is outside the "
285 "range of the original data.\n") +
286 "original data range: " + boost::lexical_cast<std::string>(xOld.front()) + " to " +
287 boost::lexical_cast<std::string>(xOld.back()) + "\n" +
288 "range to try to interpolate to " + boost::lexical_cast<std::string>(xNew.front()) +
289 " to " + boost::lexical_cast<std::string>(xNew.back()));
290 }
291 }
292
293 // Can Interpolate
294 // Create Histogram
295 Histogram newHistogram{xNew, Frequencies(xNew.size() - 1)};
296
297 auto &yNew = newHistogram.mutableY();
298 auto &eNew = newHistogram.mutableE();
299
300 if ((!goodRangeLow) || (!goodRangeHigh)) {
301 g_log.information() << "One or more points in the interpolation are near "
302 "the boundary of the input data, these points will "
303 "have slightly less accuracy\n";
304 }
305
306 // get the GSL to allocate the memory
307 const size_t nPoints = oldIn2 - oldIn1 + 1;
308 std::span<double const> xSpan(xCensOld.cbegin() + oldIn1, nPoints);
309 std::span<double const> ySpan(yOld.cbegin() + oldIn1, nPoints);
310 std::span<double const> xNewSpan(xCensNew.cbegin(), xCensNew.cend());
312 std::transform(xCensNew.cbegin(), xCensNew.cend(), eNew.begin(),
313 [this, &xCensOld, &eOld](double x) { return this->estimateError(xCensOld, eOld, x); });
314 return newHistogram;
315}
325Histogram InterpolatingRebin::noInterpolation(const Histogram &oldHistogram, const BinEdges &xNew) const {
326 Histogram newHistogram{xNew, Frequencies(xNew.size() - 1)};
327 auto &yNew = newHistogram.mutableY();
328 auto &eNew = newHistogram.mutableE();
329
330 yNew.assign(yNew.size(), oldHistogram.y().front());
331
332 const auto &xPointData = oldHistogram.points();
333 const auto &eOld = oldHistogram.e();
334
335 // -1 because xNew.size is 1 bigger than eNew
336 std::transform(xNew.cbegin(), xNew.cend() - 1, eNew.begin(),
337 [&](double x) { return estimateError(xPointData, eOld, x); });
338
339 return newHistogram;
340}
352double InterpolatingRebin::estimateError(const Points &xsOld, const HistogramE &esOld, const double xNew) const {
353
354 // find the first point in the array that has a higher value of x, we'll base
355 // some of the error estimate on the error on this point
356
357 const size_t indAbove = std::lower_bound(xsOld.begin(), xsOld.end(), xNew) - xsOld.begin();
358
359 // if the point's x-value is out of the range covered by the x-values in the
360 // input data return the error value at the end of the range
361 if (indAbove == 0) {
362 return esOld.front();
363 }
364 // xsOld is 1 longer than xsOld
365 if (indAbove >= esOld.size()) { // cubicInterpolation() checks that that there
366 // are no empty histograms
367 return esOld.back();
368 }
369
370 const double error1 = esOld[indAbove];
371 // ratio of weightings will be inversely proportional to the distance between
372 // the points
373 double weight1 = xsOld[indAbove] - xNew;
374 // check if the points are close enough agnoring any spurious effects that can
375 // occur with exact comparisons of floating point numbers
376 if (weight1 < 1e-100) {
377 // the point is on an input point, all the weight is on this point ignore
378 // the other
379 return error1;
380 }
381 weight1 = 1 / weight1;
382
383 // if p were zero lower_bound must have found xCensNew <= xCensOld.front() but
384 // in that situation we should have exited before now
385 const double error2 = esOld[indAbove - 1];
386 double weight2 = xNew - xsOld[indAbove - 1];
387 if (weight2 < 1e-100) {
388 // the point is on an input point, all the weight is on this point ignore
389 // the other
390 return error2;
391 }
392 weight2 = 1 / weight2;
393
394 return (weight1 * error1 + weight2 * error2) / (weight1 + weight2);
395}
396
397} // namespace Mantid::Algorithms
#define DECLARE_ALGORITHM(classname)
Definition Algorithm.h:538
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.
Helper class for reporting progress from algorithms.
Definition Progress.h:25
A property class for workspaces.
void outputYandEValues(const API::MatrixWorkspace_const_sptr &inputW, const HistogramData::BinEdges &XValues_new, const API::MatrixWorkspace_sptr &outputW)
Calls the interpolation function for each histogram in the workspace.
void exec() override
Executes the rebin algorithm.
HistogramData::Histogram noInterpolation(const HistogramData::Histogram &oldHistogram, const HistogramData::BinEdges &xNew) const
This can be used whenever the original spectrum is filled with only one value.
std::map< std::string, std::string > validateInputs() override
Overwrites the parent (Rebin) validateInputs Avoids all bother about binning modes and powers.
double estimateError(const HistogramData::Points &xsOld, const HistogramData::HistogramE &esOld, const double xNew) const
Estimates the error on each interpolated point by assuming it is similar to the errors in near by inp...
HistogramData::Histogram cubicInterpolation(const HistogramData::Histogram &oldHistogram, const HistogramData::BinEdges &xNew) const
Uses cubic splines to interpolate the mean rate of change of the integral over the inputed data bins ...
void init() override
Overwrites the parent (Rebin) init method These properties will be declares instead.
void propagateMasks(const API::MatrixWorkspace_const_sptr &inputWS, const API::MatrixWorkspace_sptr &outputWS, const int hist, const bool IgnoreBinErrors=false)
Takes the masks in the input workspace and apportions the weights into the new bins that overlap with...
Definition Rebin.cpp:437
static std::vector< double > rebinParamsFromInput(const std::vector< double > &inParams, const API::MatrixWorkspace &inputWS, Kernel::Logger &logger, const std::string &binModeName="Default")
Return the rebin parameters from a user input.
Definition Rebin.cpp:76
Support for a property that holds an array of values.
static std::vector< Y > getSplinedYValues(std::span< X const > newX, std::span< X const > x, std::span< Y const > y)
Definition Spline.h:84
IPropertyManager * setProperty(const std::string &name, const T &value)
Templated method to set the value of a PropertyWithValue.
void debug(const std::string &msg)
Logs at debug level.
Definition Logger.cpp:145
void error(const std::string &msg)
Logs at error level.
Definition Logger.cpp:108
void information(const std::string &msg)
Logs at information level.
Definition Logger.cpp:136
void report()
Increments the loop counter by 1, then sends the progress notification on behalf of its algorithm.
std::shared_ptr< const MatrixWorkspace > MatrixWorkspace_const_sptr
shared pointer to the matrix workspace base class (const version)
std::shared_ptr< MatrixWorkspace > MatrixWorkspace_sptr
shared pointer to the matrix workspace base class
Kernel::Logger g_log("DetermineSpinStateOrder")
void MANTID_KERNEL_DLL convertToBinCentre(const std::vector< double > &bin_edges, std::vector< double > &bin_centres)
Convert an array of bin boundaries to bin center values.
bool MANTID_KERNEL_DLL isConstantValue(const std::vector< double > &arra)
Assess if all the values in the vector are equal or if there are some different values.
std::size_t 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.
static void makeDistribution(const MatrixWorkspace_sptr &workspace, const bool forwards=true)
Divides the data in a workspace by the bin width to make it a distribution.
@ Input
An input workspace.
Definition Property.h:53
@ Output
An output workspace.
Definition Property.h:54