From 79183ebb79025d068076e849ad2cb0bd12db49d4 Mon Sep 17 00:00:00 2001 From: Martyn Gigg <martyn.gigg@stfc.ac.uk> Date: Fri, 24 Jun 2011 12:08:35 +0000 Subject: [PATCH] The performance can be improved but Rebin2D should give the correct values with the exception of masked bins at the moment. Re #3197 --- .../Framework/Algorithms/CMakeLists.txt | 9 +- .../Algorithms/inc/MantidAlgorithms/Rebin2D.h | 42 +++++ .../Framework/Algorithms/src/Rebin2D.cpp | 155 ++++++++++++++-- .../Framework/Algorithms/test/Rebin2DTest.h | 174 ++++++++++++++++++ 4 files changed, 358 insertions(+), 22 deletions(-) create mode 100644 Code/Mantid/Framework/Algorithms/test/Rebin2DTest.h diff --git a/Code/Mantid/Framework/Algorithms/CMakeLists.txt b/Code/Mantid/Framework/Algorithms/CMakeLists.txt index 6f69c8afade..6ad803204f3 100644 --- a/Code/Mantid/Framework/Algorithms/CMakeLists.txt +++ b/Code/Mantid/Framework/Algorithms/CMakeLists.txt @@ -1,4 +1,5 @@ set ( SRC_FILES + # src/Q1DTOF.cpp src/AbsorptionCorrection.cpp src/AddSampleLog.cpp src/AlignDetectors.cpp @@ -111,12 +112,12 @@ set ( SRC_FILES src/PowerLawCorrection.cpp src/Q1D.cpp src/Q1D2.cpp -# src/Q1DTOF.cpp src/Q1DWeighted.cpp src/Qxy.cpp src/ReadGroupsFromFile.cpp src/RealFFT.cpp src/Rebin.cpp + src/Rebin2D.cpp src/RebinToWorkspace.cpp src/Rebunch.cpp src/Regroup.cpp @@ -152,6 +153,7 @@ set ( SRC_FILES ) set ( INC_FILES + # inc/MantidAlgorithms/Q1DTOF.h inc/MantidAlgorithms/AbsorptionCorrection.h inc/MantidAlgorithms/AddSampleLog.h inc/MantidAlgorithms/AlignDetectors.h @@ -265,12 +267,12 @@ set ( INC_FILES inc/MantidAlgorithms/PowerLawCorrection.h inc/MantidAlgorithms/Q1D.h inc/MantidAlgorithms/Q1D2.h -# inc/MantidAlgorithms/Q1DTOF.h inc/MantidAlgorithms/Q1DWeighted.h inc/MantidAlgorithms/Qxy.h inc/MantidAlgorithms/ReadGroupsFromFile.h inc/MantidAlgorithms/RealFFT.h inc/MantidAlgorithms/Rebin.h + inc/MantidAlgorithms/Rebin2D.h inc/MantidAlgorithms/RebinToWorkspace.h inc/MantidAlgorithms/Rebunch.h inc/MantidAlgorithms/Regroup.h @@ -405,12 +407,13 @@ set ( TEST_FILES test/PolynomialCorrectionTest.h test/PowerLawCorrectionTest.h test/PowerTest.h - test/Q1DTest.h test/Q1D2Test.h + test/Q1DTest.h test/Q1DWeightedTest.h test/QxyTest.h test/ReadGroupsFromFileTest.h test/RealFFTTest.h + test/Rebin2DTest.h test/RebinTest.h test/RebinToWorkspaceTest.h test/RebunchTest.h diff --git a/Code/Mantid/Framework/Algorithms/inc/MantidAlgorithms/Rebin2D.h b/Code/Mantid/Framework/Algorithms/inc/MantidAlgorithms/Rebin2D.h index 6c92efdf7c2..767353dea12 100644 --- a/Code/Mantid/Framework/Algorithms/inc/MantidAlgorithms/Rebin2D.h +++ b/Code/Mantid/Framework/Algorithms/inc/MantidAlgorithms/Rebin2D.h @@ -9,6 +9,14 @@ namespace Mantid { + //------------------------------------------------------------------------------ + // Forward declarations + //------------------------------------------------------------------------------ + namespace Geometry + { + class ConvexPolygon; + } + namespace Algorithms { @@ -48,12 +56,46 @@ namespace Mantid virtual const std::string category() const { return "Rebin";} private: + /// A struct to store information about an intersection + struct BinWithWeight + { + /** Constructor + * @param i :: The index in the Y direction of the data bin + * @param j :: The index in the X direction of the data bin + * @param pointWeight :: The weight this point carries + */ + BinWithWeight(const size_t i, const size_t j, const double pointWeight) + : yIndex(i), xIndex(j), weight(pointWeight) {} + /// The index in the Y direction of the data bin + size_t yIndex; + /// The index in the X direction of the data bin + size_t xIndex; + /// The weight this point carries + double weight; + }; + /// Sets documentation strings for this algorithm virtual void initDocs(); /// Initialise the properties void init(); /// Run the algorithm void exec(); + /// Create a polygon + Geometry::ConvexPolygon createPolygon(const double x_i, const double x_ip1, + const double y_i, const double y_ip1) const; + /// Calculate the Y and E values for the given possible overlap + std::pair<double,double> calculateYE(API::MatrixWorkspace_const_sptr inputWS, + const MantidVec & oldYBins, + const Geometry::ConvexPolygon & outputPoly) const; + /// Find the overlap of the inputWS with the given polygon + std::vector<BinWithWeight> findIntersections(const MantidVec & oldAxis1, + const MantidVec & oldAxis2, + const Geometry::ConvexPolygon & poly) const; + /// Setup the output workspace + API::MatrixWorkspace_sptr createOutputWorkspace(API::MatrixWorkspace_const_sptr parent, + MantidVec &newXBins, + MantidVec &newYBins) const; + }; } // namespace Algorithms diff --git a/Code/Mantid/Framework/Algorithms/src/Rebin2D.cpp b/Code/Mantid/Framework/Algorithms/src/Rebin2D.cpp index 9b1da309b4d..884e702c8c6 100644 --- a/Code/Mantid/Framework/Algorithms/src/Rebin2D.cpp +++ b/Code/Mantid/Framework/Algorithms/src/Rebin2D.cpp @@ -7,6 +7,8 @@ #include "MantidKernel/VectorHelper.h" #include "MantidAPI/WorkspaceProperty.h" #include "MantidAPI/NumericAxis.h" +#include "MantidGeometry/Math/ConvexPolygon.h" +#include "MantidGeometry/Math/PolygonIntersection.h" namespace Mantid { @@ -14,10 +16,13 @@ namespace Mantid { // Register the algorithm into the AlgorithmFactory - //DECLARE_ALGORITHM(Rebin2D) + DECLARE_ALGORITHM(Rebin2D) + + using namespace API; + using Geometry::ConvexPolygon; + using Geometry::Vertex2DList; + using Kernel::V2D; - using API::MatrixWorkspace_const_sptr; - //-------------------------------------------------------------------------- // Public methods //-------------------------------------------------------------------------- @@ -33,6 +38,9 @@ namespace Mantid "new bin on the workspace"); } + //-------------------------------------------------------------------------- + // Private methods + //-------------------------------------------------------------------------- /** * Initialize the algorithm's properties. */ @@ -52,29 +60,138 @@ namespace Mantid declareProperty(new ArrayProperty<double>("Axis2Binning", new RebinParamsValidator), docString); } - //---------------------------------------------------------------------------------------------- - /** Execute the algorithm. + /** + * Execute the algorithm. */ void Rebin2D::exec() { + // Information to form input grid MatrixWorkspace_const_sptr inputWS = getProperty("InputWorkspace"); + NumericAxis *oldAxis2 = dynamic_cast<API::NumericAxis*>(inputWS->getAxis(1)); + if( !oldAxis2 ) + { + throw std::invalid_argument("Vertical axis is not a numeric axis, cannot rebin. " + "If it is a spectra axis try running ConvertSpectrumAxis first."); + } + const std::vector<double> oldYBins = oldAxis2->createBinBoundaries(); - // We require two grids. The input workspace has axis 1 as non-histogram data so we - // need to make bins out of axis1. - const MantidVec oldXBins = inputWS->readX(0); - const std::vector<double> oldYBins = - dynamic_cast<API::NumericAxis*>(inputWS->getAxis(1))->createBinBoundaries(); - // Output bins + // Output grid and workspace. Fills in the new X and Y bin vectors MantidVecPtr newXBins; - const int nXValues = - Kernel::VectorHelper::createAxisFromRebinParams(getProperty("Axis1Binning"), newXBins.access()); - MantidVecPtr newYBins; - const int nYValues = - Kernel::VectorHelper::createAxisFromRebinParams(getProperty("Axis2Binning"), newYBins.access()); - assert(nXValues > 0); - assert(nYValues > 0); - + MantidVec newYBins; + MatrixWorkspace_sptr outputWS = createOutputWorkspace(inputWS, newXBins.access(), newYBins); + const int64_t numHist = static_cast<int64_t>(outputWS->getNumberHistograms()); + const int64_t numSig = static_cast<int64_t>(outputWS->blocksize()); + Axis* newAxis2 = outputWS->getAxis(1); + + Progress progress(this,0.0,1.0, numHist); + // For each box on the output workspace find the overlapping boxes on the input workspac + PARALLEL_FOR2(inputWS,outputWS) + for(int64_t i = 0; i < numHist; ++i) + { + PARALLEL_START_INTERUPT_REGION + + outputWS->setX(i, newXBins); + // References to the Y and E data + MantidVec & outY = outputWS->dataY(i); + MantidVec & outE = outputWS->dataE(i); + // The Y axis value is the bin centre of the new Y bins + const double y_i = newYBins[i]; + const double y_ip1 = newYBins[i+1]; + newAxis2->setValue(i, 0.5*(y_i + y_ip1)); + + // For each bin on the new workspace, test compute overlap + for(int64_t j = 0; j < numSig; ++j) + { + ConvexPolygon newBin = ConvexPolygon((*newXBins)[j],(*newXBins)[j+1], y_i, y_ip1); + std::pair<double,double> contrib = calculateYE(inputWS, oldYBins, newBin); + outY[j] = contrib.first; + outE[j] = contrib.second; + } + progress.report(); + PARALLEL_END_INTERUPT_REGION + } + PARALLEL_CHECK_INTERUPT_REGION + + setProperty("OutputWorkspace", outputWS); + } + + /** + * Calculate the Y and E values for the given possible overlap + * @param inputWS :: A pointer to the inputWS + * @param oldYBins :: A reference to the set of Y bin boundaries on the input WS + * @param newBin :: A reference to a polygon to test for overlap + */ + std::pair<double,double> + Rebin2D::calculateYE(API::MatrixWorkspace_const_sptr inputWS, + const MantidVec & oldYBins, const ConvexPolygon & newBin) const + { + const MantidVec & oldXBins = inputWS->readX(0); + // Build a list intersection locations in terms of workspace indices + // along with corresponding weights from that location + std::vector<BinWithWeight> overlaps = findIntersections(oldXBins, oldYBins, newBin); + double totalY(0.0), totalE(0.0); + std::vector<BinWithWeight>::const_iterator iend = overlaps.end(); + for(std::vector<BinWithWeight>::const_iterator itr = overlaps.begin(); + itr != iend; ++itr) + { + totalY += inputWS->readY(itr->yIndex)[itr->xIndex] * itr->weight; + totalE += std::pow(inputWS->readE(itr->yIndex)[itr->xIndex] * itr->weight, 2); + } + return std::make_pair(totalY,std::sqrt(totalE)); + } + + + /** + * Find the overlap of the inputWS with the given polygon + * @param oldAxis1 :: Axis 1 bin boundaries from the input grid + * @param oldAxis2 :: Axis 2 bin boundaries from the input grid + * @param newPoly :: The new polygon to test + * @returns A list of intersection locations with weights of the overlap + */ + std::vector<Rebin2D::BinWithWeight> + Rebin2D::findIntersections(const MantidVec & oldAxis1, const MantidVec & oldAxis2, + const Geometry::ConvexPolygon & newPoly) const + { + std::vector<BinWithWeight> overlaps; + overlaps.reserve(5); // Have a guess at a posible limit + + const size_t nxpoints(oldAxis1.size()-1), nypoints(oldAxis2.size()-1); + Vertex2DList vertices(4); + for(size_t i = 0; i < nypoints; ++i) + { + for(size_t j = 0; j < nxpoints; ++j) + { + ConvexPolygon oldPoly(oldAxis1[j], oldAxis1[j+1], oldAxis2[i], oldAxis2[i+1]); + try + { + ConvexPolygon overlap = chasingEdgeIntersect(newPoly, oldPoly); + overlaps.push_back(BinWithWeight(i,j,overlap.area()/oldPoly.area())); + } + catch(std::runtime_error &) + {} + } + } + return overlaps; + } + + /** + * Setup the output workspace + * @param parent :: A pointer to the input workspace + * @param newXBins[out] :: An output vector to be filled with the new X bin boundaries + * @param newYBins[out] :: An output vector to be filled with the new Y bin boundaries + * @return A pointer to the output workspace + */ + MatrixWorkspace_sptr Rebin2D::createOutputWorkspace(MatrixWorkspace_const_sptr parent, + MantidVec & newXBins, + MantidVec & newYBins) const + { + using Kernel::VectorHelper::createAxisFromRebinParams; + // First create the two sets of bin boundaries + const int newXSize = createAxisFromRebinParams(getProperty("Axis1Binning"), newXBins); + const int newYSize = createAxisFromRebinParams(getProperty("Axis2Binning"), newYBins); + // and now the workspace + return WorkspaceFactory::Instance().create(parent,newYSize-1,newXSize,newXSize-1); } } // namespace Algorithms diff --git a/Code/Mantid/Framework/Algorithms/test/Rebin2DTest.h b/Code/Mantid/Framework/Algorithms/test/Rebin2DTest.h new file mode 100644 index 00000000000..8619276dc69 --- /dev/null +++ b/Code/Mantid/Framework/Algorithms/test/Rebin2DTest.h @@ -0,0 +1,174 @@ +#ifndef MANTID_ALGORITHMS_REBIN2DTEST_H_ +#define MANTID_ALGORITHMS_REBIN2DTEST_H_ + +#include <cxxtest/TestSuite.h> +#include "MantidAlgorithms/Rebin2D.h" +#include "MantidTestHelpers/WorkspaceCreationHelper.h" +#include "MantidAPI/NumericAxis.h" + +#include "MantidKernel/Timer.h" + +using Mantid::Algorithms::Rebin2D; +using namespace Mantid::API; + +namespace +{ + /** + * Shared methods between the unit test and performance test + */ + + /// Return the input workspace. All Y values are 2 and E values sqrt(2) + MatrixWorkspace_sptr makeInputWS(const bool large = false) + { + size_t nhist(0), nbins(0); + double x0(0.0), deltax(0.0); + + if( large ) + { + nhist = 200; + nbins = 200; + x0 = 100.; + deltax = 200.; + } + else + { + nhist = 10; + nbins = 10; + x0 = 5.0; + deltax = 1.0; + } + + MatrixWorkspace_sptr ws = WorkspaceCreationHelper::Create2DWorkspaceBinned(int(nhist), int(nbins), x0, deltax); + + // We need something other than a spectrum axis, call this one theta + NumericAxis* const thetaAxis = new NumericAxis(nhist); + for(size_t i = 0; i < nhist; ++i) + { + thetaAxis->setValue(i, static_cast<double>(i)); + } + ws->replaceAxis(1, thetaAxis); + return ws; + } + + MatrixWorkspace_sptr runAlgorithm(MatrixWorkspace_sptr inputWS, + const std::string & axis1Params, + const std::string & axis2Params) + { + // Name of the output workspace. + std::string outWSName("Rebin2DTest_OutputWS"); + + Rebin2D alg; + TS_ASSERT_THROWS_NOTHING( alg.initialize() ) + TS_ASSERT( alg.isInitialized() ) + TS_ASSERT_THROWS_NOTHING( alg.setProperty("InputWorkspace", inputWS) ); + TS_ASSERT_THROWS_NOTHING( alg.setPropertyValue("OutputWorkspace", outWSName) ); + TS_ASSERT_THROWS_NOTHING( alg.setPropertyValue("Axis1Binning", axis1Params) ); + TS_ASSERT_THROWS_NOTHING( alg.setPropertyValue("Axis2Binning", axis2Params) ); + TS_ASSERT_THROWS_NOTHING( alg.execute(); ); + TS_ASSERT( alg.isExecuted() ); + + MatrixWorkspace_sptr outputWS = boost::dynamic_pointer_cast<MatrixWorkspace>( + AnalysisDataService::Instance().retrieve(outWSName)); + TS_ASSERT(outputWS); + return outputWS; + } + +} + +class Rebin2DTest : public CxxTest::TestSuite +{ +public: + + void test_Init() + { + Rebin2D alg; + TS_ASSERT_THROWS_NOTHING( alg.initialize() ) + TS_ASSERT( alg.isInitialized() ) + } + + void test_Rebin2D_With_Axis2_Unchanged() + { + MatrixWorkspace_sptr inputWS = makeInputWS(); //10 histograms, 10 bins + MatrixWorkspace_sptr outputWS = runAlgorithm(inputWS, "5.,2.,15.", "-0.5,1,9.5"); + + // Check values + const size_t nxvalues(6), nhist(10); + TS_ASSERT_EQUALS(outputWS->getNumberHistograms(), nhist); + // Axis sizes + TS_ASSERT_EQUALS(outputWS->getAxis(0)->length(), nxvalues); + TS_ASSERT_EQUALS(outputWS->getAxis(1)->length(), nhist); + TS_ASSERT_EQUALS(outputWS->readX(0).size(), nxvalues); + TS_ASSERT_EQUALS(outputWS->readY(0).size(), nxvalues - 1); + + Mantid::API::Axis *newYAxis = outputWS->getAxis(1); + for(size_t i = 0; i < nhist; ++i) + { + for(size_t j = 0; j < nxvalues - 1; ++j) + { + const double expected(5.0 + 2.0*static_cast<double>(j)); + TS_ASSERT_DELTA(outputWS->readX(i)[j], expected, DBL_EPSILON); + TS_ASSERT_DELTA(outputWS->readY(i)[j], 4.0, DBL_EPSILON); + TS_ASSERT_DELTA(outputWS->readE(i)[j], 2.0, DBL_EPSILON); + } + // Final X boundary + TS_ASSERT_DELTA(outputWS->readX(i)[nxvalues-1], 15.0, DBL_EPSILON); + // The new Y axis value should be the centre point bin values + TS_ASSERT_DELTA((*newYAxis)(i), static_cast<double>(i), DBL_EPSILON); + } + + // Clean up + AnalysisDataService::Instance().remove(outputWS->getName()); + } + + void test_Rebin2D_With_Axis1_Unchanged() + { + MatrixWorkspace_sptr inputWS = makeInputWS(); //10 histograms, 10 bins + MatrixWorkspace_sptr outputWS = runAlgorithm(inputWS, "5.,1.,15.", "-0.5,2,9.5"); + // Check values + const size_t nxvalues(11), nhist(5); + TS_ASSERT_EQUALS(outputWS->getNumberHistograms(), nhist); + // Axis sizes + TS_ASSERT_EQUALS(outputWS->getAxis(0)->length(), nxvalues); + TS_ASSERT_EQUALS(outputWS->getAxis(1)->length(), nhist); + TS_ASSERT_EQUALS(outputWS->readX(0).size(), nxvalues); + TS_ASSERT_EQUALS(outputWS->readY(0).size(), nxvalues - 1); + + Mantid::API::Axis *newYAxis = outputWS->getAxis(1); + for(size_t i = 0; i < nhist; ++i) + { + for(size_t j = 0; j < nxvalues - 1; ++j) + { + TS_ASSERT_DELTA(outputWS->readX(i)[j], 5.0 + static_cast<double>(j), DBL_EPSILON); + TS_ASSERT_DELTA(outputWS->readY(i)[j], 4.0, DBL_EPSILON); + TS_ASSERT_DELTA(outputWS->readE(i)[j], 2.0, DBL_EPSILON); + } + // Final X boundary + TS_ASSERT_DELTA(outputWS->readX(i)[nxvalues-1], 15.0, DBL_EPSILON); + // The new Y axis value should be the centre point bin values + TS_ASSERT_DELTA((*newYAxis)(i), 0.5 + 2.0*static_cast<double>(i), DBL_EPSILON); + } + } +}; + + +//------------------------------------------------------------------------------ +// Performance test +//------------------------------------------------------------------------------ + +class Rebin2DTestPerformance : public CxxTest::TestSuite +{ + +public: + + void test_On_Large_Workspace() + { + MatrixWorkspace_sptr inputWS = makeInputWS(); + runAlgorithm(inputWS, "200,250,40000", "-0.5,5,199.5"); + } + + +}; + + +#endif /* MANTID_ALGORITHMS_REBIN2DTEST_H_ */ + -- GitLab