//---------------------------------------------------------------------- // Includes //---------------------------------------------------------------------- #include "MantidAlgorithms/InterpolatingRebin.h" #include "MantidKernel/VectorHelper.h" #include <gsl/gsl_errno.h> #include <gsl/gsl_interp.h> #include <gsl/gsl_spline.h> namespace Mantid { namespace Algorithms { // Register the class into the algorithm factory DECLARE_ALGORITHM(InterpolatingRebin) using namespace Kernel; using namespace API; /** Only calls its parent's (SimpleRebin) init() * */ void InterpolatingRebin::init() { SimpleRebin::init(); } /** Executes the rebin algorithm * * @throw runtime_error Thrown if the bin range does not intersect the range of the input workspace */ void InterpolatingRebin::exec() { // retrieve the properties std::vector<double> rb_params=getProperty("Params"); // Get the input workspace MatrixWorkspace_sptr inputW = getProperty("InputWorkspace"); //this calculation requires a distribution workspace but deal with the situation when we don't get this const bool distCon = ! inputW->isDistribution(); if (distCon) { g_log.debug() << "Converting the input workspace to a distribution\n"; WorkspaceHelpers::makeDistribution(inputW); } DataObjects::Histogram1D::RCtype XValues_new; // create new output X axis const int ntcnew = VectorHelper::createAxisFromRebinParams(rb_params,XValues_new.access()); const int nHists = inputW->getNumberHistograms(); // make output Workspace the same type is the input, but with new length of signal array MatrixWorkspace_sptr outputW = WorkspaceFactory::Instance().create(inputW, nHists, ntcnew, ntcnew-1); // Copy over the 'vertical' axis if (inputW->axes() > 1) outputW->replaceAxis( 1, inputW->getAxis(1)->clone(outputW.get()) ); //evaluate the rebinned data outputXandEValues(inputW, XValues_new, outputW); //check if there was a convert to distribution done previously if (distCon) { g_log.debug() << "Converting the input and output workspaces _from_ distributions\n"; WorkspaceHelpers::makeDistribution(inputW, false); // the calculation produces a distribution workspace but if they passed a non-distribution workspace they maybe not expect it, so convert back to the same form that was given WorkspaceHelpers::makeDistribution(outputW, false); } outputW->isDistribution( ! distCon ); // Now propagate any masking correctly to the output workspace // More efficient to have this in a separate loop because // MatrixWorkspace::maskBins blocks multi-threading for (int i=0; i < nHists; ++i) { if ( inputW->hasMaskedBins(i) ) // Does the current spectrum have any masked bins? { this->propagateMasks(inputW,outputW,i); } } for (int i=0; i < outputW->axes(); ++i) { outputW->getAxis(i)->unit() = inputW->getAxis(i)->unit(); } // Assign it to the output workspace property setProperty("OutputWorkspace",outputW); } /** Calls the interpolation function for each histogram in the workspace * @param[in] inputW workspace with un-interpolated data * @param[in] XValues_new new x-values to interpolated to * @param[out] outputW this will contain the interpolated data, the lengths of the histograms must corrospond with the number of x-values in XValues_new */ void InterpolatingRebin::outputXandEValues(API::MatrixWorkspace_const_sptr inputW, const DataObjects::Histogram1D::RCtype &XValues_new, API::MatrixWorkspace_sptr outputW) { g_log.debug() << "Preparing to calculate y-values using splines and estimate errors\n"; // prepare to use GSL functions but don't let them terminate Mantid gsl_error_handler_t * old_handler = gsl_set_error_handler(NULL); const int histnumber = inputW->getNumberHistograms(); Progress prog(this,0.0,1.0,histnumber); PARALLEL_FOR2(inputW,outputW) for (int hist=0; hist < histnumber;++hist) { PARALLEL_START_INTERUPT_REGION // get const references to input Workspace arrays (no copying) const MantidVec& XValues = inputW->readX(hist); const MantidVec& YValues = inputW->readY(hist); const MantidVec& YErrors = inputW->readE(hist); //get references to output workspace data (no copying) MantidVec& YValues_new=outputW->dataY(hist); MantidVec& YErrors_new=outputW->dataE(hist); try { // output data arrays are implicitly filled by function cubicInterpolation(XValues, YValues, YErrors, *XValues_new, YValues_new, YErrors_new); } catch (std::exception& ex) { g_log.error() << "Error in rebin function: " << ex.what() << std::endl; throw; } // Populate the output workspace X values outputW->setX(hist, XValues_new); prog.report(); PARALLEL_END_INTERUPT_REGION } PARALLEL_CHECK_INTERUPT_REGION gsl_set_error_handler(old_handler); } /**Uses cubic splines to interpolate the mean rate of change of the integral * over the inputed data bins to that for the user supplied bins. * Note that this algorithm was inplemented to provide a little more resolution * on high count rate data. Whether it is more accurate than the standard rebin * for all, or your, application needs more thought. * The input data must be a distribution (proportional to the rate of change e.g. * raw_counts/bin_widths) but note that these mean rate of counts data * are integrals not (instanteously) sampled data. The error values on each point * are a weighted mean of the error values from the surrounding input data. This * makes sense if the interpolation error is low compared to the statistical * errors on each input data point. The weighting is inversely proportional to * the distance from the original data point to the new interpolated one. * * @param[in] xOld the x-values of the data that will be intepolated * @param[in] yOld the data's y-values that corrospond to the x-values, must be 1 element shorter than xOld. * @param[in] eOld the error on each y-value, must be same length as yOld. * @param[in] xNew x-values to rebin to, must be monotonically increasing * @param[out] yNew is overwritten with the algorithm output. Must be allocated and 1 element shorter than xnew. * @param[out] eNew is overwritten with errors from the errors on the nearest input data points. Must be allocated with the same number of points as ynew * @throw runtime_error if there is a problem executing one of the GSL functions * @throw invalid_argument if any output x-values are outside the range of input x-values **/ void InterpolatingRebin::cubicInterpolation(const MantidVec &xOld, const MantidVec &yOld, const MantidVec &eOld, const MantidVec& xNew, MantidVec &yNew, MantidVec &eNew) const { // Make sure y and e vectors are of correct sizes const size_t size_old = yOld.size(); if (size_old != (xOld.size() - 1) || size_old != eOld.size() ) throw std::runtime_error("rebin: y and error vectors should be of same size & 1 shorter than x"); const size_t size_new = yNew.size(); if (size_new != (xNew.size() - 1) || size_new != eNew.size() ) throw std::runtime_error("rebin: y and error vectors should be of same size & 1 shorter than x"); // get the bin centres of the input data std::vector<double> xCensOld(size_new); VectorHelper::convertToBinCentre(xOld, xCensOld); // the centres of the output data std::vector<double> xCensNew(size_new); VectorHelper::convertToBinCentre(xNew, xCensNew); // find the range of input values whose x-values just suround the output x-values size_t oldIn1 = std::lower_bound(xCensOld.begin(), xCensOld.end(), xCensNew.front()) - xCensOld.begin() - 1; size_t oldIn2 = std::lower_bound(xCensOld.begin(), xCensOld.end(), xCensNew.back()) - xCensOld.begin(); //bring one point before and one point after into the inpolation to reduce any errors coming in from the edge oldIn1 --; oldIn2 ++; //check that the end points are all within the arrays if ( oldIn1<0 || oldIn2>=size_old || oldIn1>oldIn2 ) { throw std::invalid_argument("Problem with the requested x-values to intepolate to: There must be at\nleast two input data points below the range of intepolation points and\ntwo higher. Also the intepolation points must have monatomically increasing x-values."); } //get the GSL to allocate the memory, if this wasn't already done gsl_interp_accel *acc = gsl_interp_accel_alloc(); const size_t nPoints = oldIn2 - oldIn1 + 1; gsl_spline *spline = gsl_spline_alloc(gsl_interp_cspline, nPoints); if ( ! acc || ! spline || //GSL calculates the splines gsl_spline_init(spline, &xCensOld[oldIn1], &yOld[oldIn1], nPoints) ) { throw std::runtime_error("Error setting up GSL spline functions"); } for ( size_t i = 0; i < size_new; ++i ) { yNew[i] = gsl_spline_eval(spline, xCensNew[i], acc); //(basic) error estimate the based on a weighted mean of the errors of the surrounding input data points eNew[i] = estimateError(xCensOld, eOld, xCensNew[i]); } //for GSL to clear up its memory use gsl_spline_free (spline); gsl_interp_accel_free (acc); } /**Estimates the error on each interpolated point by assuming it is similar to the errors in * near by input data points. Output points with the same x-value as an input point have the * same error as the input point. Points between input points have a error value that is a * weighted mean of the closest input points * @param[in] xsOld x-values of the input data around the point of interested * @param[in] esOld error values for the same points in the input data as xsOld * @param[in] xNew the value of x for at the point of interest * @return the estimated error at that point */ double InterpolatingRebin::estimateError(const std::vector<double>& xsOld, const std::vector<double>& esOld, const double xNew) const { //get the index of the first point that is higher in x, we'll base some of the error estimate on the error on this point const size_t indAbove = std::lower_bound(xsOld.begin(), xsOld.end(), xNew) - xsOld.begin(); const double error1 = esOld[indAbove]; // ratio of weightings will be inversely proportional to the distance between the points double weight1 = xsOld[indAbove] - xNew; //check if the points are close enough agnoring any spurious effects that can occur with exact comparisons of floating point numbers if ( weight1 < 1e-100 ) { // the point is on an input point, all the weight is on this point ignore the other return error1; } weight1 = 1/weight1; // if p were zero lower_bound must have found xCensNew <= xCensOld.front() but in that situation we should have exited before now const double error2 = esOld[indAbove-1]; double weight2 = xNew - xsOld[indAbove-1]; if ( weight2 < 1e-100 ) { // the point is on an input point, all the weight is on this point ignore the other return error2; } weight2 = 1/weight2; return ( weight1*error1 + weight2*error2 )/( weight1 + weight2 ); } } // namespace Algorithm } // namespace Mantid