diff --git a/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/IBeamProfile.h b/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/IBeamProfile.h index 2e9c8966d52490aa6eec76ee91eb1c0bacebbe83..a9305fc807b71a2dfcfc0c9007fd6c00e1efda4b 100644 --- a/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/IBeamProfile.h +++ b/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/IBeamProfile.h @@ -8,6 +8,9 @@ namespace Mantid { namespace Kernel { class PseudoRandomNumberGenerator; } +namespace Geometry { +class BoundingBox; +} namespace Algorithms { /** @@ -44,6 +47,8 @@ public: virtual ~IBeamProfile() = default; virtual Ray generatePoint(Kernel::PseudoRandomNumberGenerator &rng) const = 0; + virtual Ray generatePoint(Kernel::PseudoRandomNumberGenerator &rng, + const Geometry::BoundingBox &) const = 0; }; } // namespace Algorithms diff --git a/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/MCInteractionVolume.h b/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/MCInteractionVolume.h index f3bff28e4818bb290e2adebac80ac357d22c4167..0654a72c9d62e0a198655e6453caed28472ee8fc 100644 --- a/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/MCInteractionVolume.h +++ b/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/MCInteractionVolume.h @@ -8,6 +8,7 @@ namespace API { class Sample; } namespace Geometry { +class BoundingBox; class Object; class SampleEnvironment; } @@ -50,6 +51,7 @@ public: // the sample MCInteractionVolume(const API::Sample &&sample) = delete; + const Geometry::BoundingBox &getBoundingBox() const; double calculateAbsorption(Kernel::PseudoRandomNumberGenerator &rng, const Kernel::V3D &startPos, const Kernel::V3D &direc, diff --git a/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/RectangularBeamProfile.h b/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/RectangularBeamProfile.h index bf861860229b4be088c47417de4a10c5fcb1f156..f9d9c874561e45ed6ef500d7a71102196acc66bb 100644 --- a/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/RectangularBeamProfile.h +++ b/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/RectangularBeamProfile.h @@ -46,6 +46,9 @@ public: IBeamProfile::Ray generatePoint(Kernel::PseudoRandomNumberGenerator &rng) const override; + IBeamProfile::Ray + generatePoint(Kernel::PseudoRandomNumberGenerator &rng, + const Geometry::BoundingBox &bounds) const override; private: const unsigned short m_upIdx; diff --git a/Framework/Algorithms/src/SampleCorrections/MCAbsorptionStrategy.cpp b/Framework/Algorithms/src/SampleCorrections/MCAbsorptionStrategy.cpp index 51ad8d9627271d4076425f749b7740abf28d012d..2bac0f324cfc9055ac3db231f76bd1df9e44ff17 100644 --- a/Framework/Algorithms/src/SampleCorrections/MCAbsorptionStrategy.cpp +++ b/Framework/Algorithms/src/SampleCorrections/MCAbsorptionStrategy.cpp @@ -3,6 +3,14 @@ #include "MantidKernel/PseudoRandomNumberGenerator.h" #include "MantidKernel/V3D.h" +#include "MantidAlgorithms/SampleCorrections/RectangularBeamProfile.h" +#include "MantidGeometry/Objects/Object.h" + +namespace { +/// Maximum number of tries to generate a track through the sample +unsigned int MAX_EVENT_ATTEMPTS = 100; +} + namespace Mantid { using Kernel::PseudoRandomNumberGenerator; @@ -38,15 +46,27 @@ MCAbsorptionStrategy::calculate(Kernel::PseudoRandomNumberGenerator &rng, // absorption within the interacting volume defined by the sample (and // environment. The final correction factor is computed as the simple average // of m_nevents of this process. - + const auto scatterBounds = m_scatterVol.getBoundingBox(); double factor(0.0); for (size_t i = 0; i < m_nevents; ++i) { - auto neutron = m_beamProfile.generatePoint(rng); - factor += - m_scatterVol.calculateAbsorption(rng, neutron.startPos, neutron.unitDir, - finalPos, lambdaBefore, lambdaAfter); + size_t attempts(0); + do { + const auto neutron = m_beamProfile.generatePoint(rng, scatterBounds); + const double wgt = m_scatterVol.calculateAbsorption( + rng, neutron.startPos, neutron.unitDir, finalPos, lambdaBefore, + lambdaAfter); + if (wgt < 0.0) { + ++attempts; + } else { + factor += wgt; + break; + } + if (attempts == MAX_EVENT_ATTEMPTS) { + throw std::runtime_error("Unable to generate valid track through " + "sample interaction volume."); + } + } while (true); } - using std::make_tuple; return make_tuple(factor / static_cast<double>(m_nevents), m_error); } diff --git a/Framework/Algorithms/src/SampleCorrections/MCInteractionVolume.cpp b/Framework/Algorithms/src/SampleCorrections/MCInteractionVolume.cpp index 8877315a237ab2f579eb7e4fb21b21be33328c8b..8045cc5c99ce939964480012eef83ad9b5547e1b 100644 --- a/Framework/Algorithms/src/SampleCorrections/MCInteractionVolume.cpp +++ b/Framework/Algorithms/src/SampleCorrections/MCInteractionVolume.cpp @@ -48,6 +48,14 @@ MCInteractionVolume::MCInteractionVolume(const API::Sample &sample) } } +/** + * Returns the axis-aligned bounding box for the volume + * @return A reference to the bounding box + */ +const Geometry::BoundingBox &MCInteractionVolume::getBoundingBox() const { + return m_sample.getBoundingBox(); +} + /** * Calculate the attenuation correction factor for given track through the * volume @@ -58,7 +66,8 @@ MCInteractionVolume::MCInteractionVolume(const API::Sample &sample) * outside of the "volume") * @param lambdaBefore Wavelength, in \f$\\A^-1\f$, before scattering * @param lambdaAfter Wavelength, in \f$\\A^-1\f$, after scattering - * @return The fraction of the beam that has been attenuated + * @return The fraction of the beam that has been attenuated. A negative number + * indicates the track was not valid. */ double MCInteractionVolume::calculateAbsorption( Kernel::PseudoRandomNumberGenerator &rng, const Kernel::V3D &startPos, @@ -80,8 +89,7 @@ double MCInteractionVolume::calculateAbsorption( nsegments += m_env->interceptSurfaces(path1); } if (nsegments == 0) { - // The track passed through nothing and so was not attenuated at all. - return 1.0; + return -1.0; } int scatterSegmentNo(1); if (nsegments != 1) { diff --git a/Framework/Algorithms/src/SampleCorrections/RectangularBeamProfile.cpp b/Framework/Algorithms/src/SampleCorrections/RectangularBeamProfile.cpp index b422615b1cca1552bf0206515e8d328305863f57..5fe679a20237a1a1956b8b3c511cbcc38ce5da9c 100644 --- a/Framework/Algorithms/src/SampleCorrections/RectangularBeamProfile.cpp +++ b/Framework/Algorithms/src/SampleCorrections/RectangularBeamProfile.cpp @@ -1,10 +1,11 @@ #include "MantidAlgorithms/SampleCorrections/RectangularBeamProfile.h" #include "MantidGeometry/Instrument/ReferenceFrame.h" +#include "MantidGeometry/Objects/BoundingBox.h" #include "MantidKernel/PseudoRandomNumberGenerator.h" #include "MantidKernel/V3D.h" namespace Mantid { - +using Kernel::V3D; namespace Algorithms { /** @@ -35,7 +36,6 @@ RectangularBeamProfile::RectangularBeamProfile( */ IBeamProfile::Ray RectangularBeamProfile::generatePoint( Kernel::PseudoRandomNumberGenerator &rng) const { - using Kernel::V3D; V3D pt; pt[m_upIdx] = m_min[m_upIdx] + rng.nextValue() * m_height; pt[m_horIdx] = m_min[m_horIdx] + rng.nextValue() * m_width; @@ -43,5 +43,32 @@ IBeamProfile::Ray RectangularBeamProfile::generatePoint( return {pt, m_beamDir}; } +/** + * Generate a random point on the profile that is within the given bounding + * area. If the point is outside the area then it is pulled to the boundary of + * the bounding area. + * @param rng A reference to a random number generator + * @param bounds A reference to the bounding area that defines the maximum + * allowed region for the generated point. + * @return An IBeamProfile::Ray describing the start and direction + */ +IBeamProfile::Ray RectangularBeamProfile::generatePoint( + Kernel::PseudoRandomNumberGenerator &rng, + const Geometry::BoundingBox &bounds) const { + auto rngRay = generatePoint(rng); + auto &rngPt = rngRay.startPos; + const V3D minBound(bounds.minPoint()), maxBound(bounds.maxPoint()); + if (rngPt[m_upIdx] > maxBound[m_upIdx]) + rngPt[m_upIdx] = maxBound[m_upIdx]; + else if (rngPt[m_upIdx] < minBound[m_upIdx]) + rngPt[m_upIdx] = minBound[m_upIdx]; + + if (rngPt[m_horIdx] > maxBound[m_horIdx]) + rngPt[m_horIdx] = maxBound[m_horIdx]; + else if (rngPt[m_horIdx] < minBound[m_horIdx]) + rngPt[m_horIdx] = minBound[m_horIdx]; + return rngRay; +} + } // namespace Algorithms } // namespace Mantid diff --git a/Framework/Algorithms/test/AnnularRingAbsorptionTest.h b/Framework/Algorithms/test/AnnularRingAbsorptionTest.h index 798baff0adc86c7930b5b2d290365cc7ac8e47c8..ac1d57638cffd3a60457e02d1dabac05e5a42737 100644 --- a/Framework/Algorithms/test/AnnularRingAbsorptionTest.h +++ b/Framework/Algorithms/test/AnnularRingAbsorptionTest.h @@ -45,9 +45,9 @@ public: const double delta(1e-08); const size_t middle_index = 4; - TS_ASSERT_DELTA(0.98887724, outWS->readY(0).front(), delta); - TS_ASSERT_DELTA(0.92954551, outWS->readY(0)[middle_index], delta); - TS_ASSERT_DELTA(0.86377145, outWS->readY(0).back(), delta); + TS_ASSERT_DELTA(0.97773712, outWS->readY(0).front(), delta); + TS_ASSERT_DELTA(0.83720057, outWS->readY(0)[middle_index], delta); + TS_ASSERT_DELTA(0.72020602, outWS->readY(0).back(), delta); } //-------------------- Failure cases -------------------------------- diff --git a/Framework/Algorithms/test/MCAbsorptionStrategyTest.h b/Framework/Algorithms/test/MCAbsorptionStrategyTest.h index df0bea3bf0eaf13edda43cfbee0b43ac1e9da1e2..0b703dedf4fffba164463197e14b7756cee76dbf 100644 --- a/Framework/Algorithms/test/MCAbsorptionStrategyTest.h +++ b/Framework/Algorithms/test/MCAbsorptionStrategyTest.h @@ -5,6 +5,7 @@ #include "MantidAlgorithms/SampleCorrections/IBeamProfile.h" #include "MantidAlgorithms/SampleCorrections/MCAbsorptionStrategy.h" +#include "MantidGeometry/Objects/BoundingBox.h" #include "MantidKernel/WarningSuppressions.h" #include "MonteCarloTesting.h" @@ -44,7 +45,7 @@ public: } const Mantid::Algorithms::IBeamProfile::Ray testRay = {V3D(-2, 0, 0), V3D(1, 0, 0)}; - EXPECT_CALL(m_testBeamProfile, generatePoint(_)) + EXPECT_CALL(m_testBeamProfile, generatePoint(_, _)) .Times(Exactly(static_cast<int>(m_nevents))) .WillRepeatedly(Return(testRay)); const V3D endPos(0.7, 0.7, 1.4); @@ -68,6 +69,9 @@ private: GCC_DIAG_OFF_SUGGEST_OVERRIDE MOCK_CONST_METHOD1(generatePoint, Ray(Mantid::Kernel::PseudoRandomNumberGenerator &)); + MOCK_CONST_METHOD2(generatePoint, + Ray(Mantid::Kernel::PseudoRandomNumberGenerator &, + const Mantid::Geometry::BoundingBox &)); GCC_DIAG_ON_SUGGEST_OVERRIDE }; diff --git a/Framework/Algorithms/test/MCInteractionVolumeTest.h b/Framework/Algorithms/test/MCInteractionVolumeTest.h index 0872aa6fc883b900ea4f92e93cd07b716998a213..ce32e772b139be86c3facfcf1392ce467be29eb2 100644 --- a/Framework/Algorithms/test/MCInteractionVolumeTest.h +++ b/Framework/Algorithms/test/MCInteractionVolumeTest.h @@ -22,6 +22,17 @@ public: //---------------------------------------------------------------------------- // Success cases //---------------------------------------------------------------------------- + void test_Bounding_Volume_Matches_Sample() { + using namespace MonteCarloTesting; + auto sample = createTestSample(TestSampleType::SolidSphere); + MCInteractionVolume interactor(sample); + + const auto sampleBox = sample.getShape().getBoundingBox(); + const auto interactionBox = interactor.getBoundingBox(); + TS_ASSERT_EQUALS(sampleBox.minPoint(), interactionBox.minPoint()); + TS_ASSERT_EQUALS(sampleBox.maxPoint(), interactionBox.maxPoint()); + } + void test_Absorption_In_Solid_Sample_Gives_Expected_Answer() { using Mantid::Kernel::V3D; using namespace MonteCarloTesting; @@ -106,7 +117,7 @@ public: Mock::VerifyAndClearExpectations(&rng); } - void test_Track_With_Zero_Intersections_Returns_Unity_Factor() { + void test_Track_With_Zero_Intersections_Returns_Negative_Factor() { using Mantid::Kernel::V3D; using namespace MonteCarloTesting; using namespace ::testing; @@ -120,10 +131,8 @@ public: auto sample = createTestSample(TestSampleType::SolidSphere); MCInteractionVolume interactor(sample); - TS_ASSERT_DELTA(1.0, - interactor.calculateAbsorption(rng, startPos, direc, endPos, - lambdaBefore, lambdaAfter), - 1e-08); + TS_ASSERT(interactor.calculateAbsorption(rng, startPos, direc, endPos, + lambdaBefore, lambdaAfter) < 0.0); } //---------------------------------------------------------------------------- diff --git a/Framework/Algorithms/test/MonteCarloAbsorptionTest.h b/Framework/Algorithms/test/MonteCarloAbsorptionTest.h index bc10128a479e5e667b90978f89fa8573c68baa6a..c44e0d78b0eff961beb293472b28e62abcc97e33 100644 --- a/Framework/Algorithms/test/MonteCarloAbsorptionTest.h +++ b/Framework/Algorithms/test/MonteCarloAbsorptionTest.h @@ -94,18 +94,18 @@ public: auto outputWS = runAlgorithm(wsProps); verifyDimensions(wsProps, outputWS); - const double delta(1e-08); + const double delta(1e-05); const size_t middle_index(4); - TS_ASSERT_DELTA(0.21339478, outputWS->y(0).front(), delta); - TS_ASSERT_DELTA(0.23415902, outputWS->y(0)[middle_index], delta); - TS_ASSERT_DELTA(0.18711438, outputWS->y(0).back(), delta); - TS_ASSERT_DELTA(0.21347241, outputWS->y(2).front(), delta); - TS_ASSERT_DELTA(0.2341577, outputWS->y(2)[middle_index], delta); - TS_ASSERT_DELTA(0.18707489, outputWS->y(2).back(), delta); - TS_ASSERT_DELTA(0.21367069, outputWS->y(4).front(), delta); - TS_ASSERT_DELTA(0.23437129, outputWS->y(4)[middle_index], delta); - TS_ASSERT_DELTA(0.18710594, outputWS->y(4).back(), delta); + TS_ASSERT_DELTA(0.019012, outputWS->y(0).front(), delta); + TS_ASSERT_DELTA(0.0021002, outputWS->y(0)[middle_index], delta); + TS_ASSERT_DELTA(0.00010066, outputWS->y(0).back(), delta); + TS_ASSERT_DELTA(0.019074, outputWS->y(2).front(), delta); + TS_ASSERT_DELTA(0.001629, outputWS->y(2)[middle_index], delta); + TS_ASSERT_DELTA(9.4268e-05, outputWS->y(2).back(), delta); + TS_ASSERT_DELTA(0.019256, outputWS->y(4).front(), delta); + TS_ASSERT_DELTA(0.0014369, outputWS->y(4)[middle_index], delta); + TS_ASSERT_DELTA(9.8238e-05, outputWS->y(4).back(), delta); } void test_Workspace_With_Just_Sample_For_Direct() { @@ -115,11 +115,12 @@ public: auto outputWS = runAlgorithm(wsProps); verifyDimensions(wsProps, outputWS); - const double delta(1e-08); + const double delta(1e-05); const size_t middle_index(4); - TS_ASSERT_DELTA(0.20488748, outputWS->y(0).front(), delta); - TS_ASSERT_DELTA(0.23469609, outputWS->y(0)[middle_index], delta); - TS_ASSERT_DELTA(0.187899, outputWS->y(0).back(), delta); + + TS_ASSERT_DELTA(0.0087756, outputWS->y(0).front(), delta); + TS_ASSERT_DELTA(0.0031353, outputWS->y(0)[middle_index], delta); + TS_ASSERT_DELTA(0.00087368, outputWS->y(0).back(), delta); } void test_Workspace_With_Just_Sample_For_Indirect() { @@ -129,11 +130,12 @@ public: auto outputWS = runAlgorithm(wsProps); verifyDimensions(wsProps, outputWS); - const double delta(1e-08); + const double delta(1e-05); const size_t middle_index(4); - TS_ASSERT_DELTA(0.20002242, outputWS->y(0).front(), delta); - TS_ASSERT_DELTA(0.23373778, outputWS->y(0)[middle_index], delta); - TS_ASSERT_DELTA(0.18742317, outputWS->y(0).back(), delta); + + TS_ASSERT_DELTA(0.0038337, outputWS->y(0).front(), delta); + TS_ASSERT_DELTA(0.0013434, outputWS->y(0)[middle_index], delta); + TS_ASSERT_DELTA(0.00019552, outputWS->y(0).back(), delta); } void test_Workspace_With_Sample_And_Container() { @@ -143,11 +145,12 @@ public: auto outputWS = runAlgorithm(wsProps); verifyDimensions(wsProps, outputWS); - const double delta(1e-08); + const double delta(1e-05); const size_t middle_index(4); - TS_ASSERT_DELTA(0.22929866, outputWS->y(0).front(), delta); - TS_ASSERT_DELTA(0.21436937, outputWS->y(0)[middle_index], delta); - TS_ASSERT_DELTA(0.23038325, outputWS->y(0).back(), delta); + + TS_ASSERT_DELTA(0.016547, outputWS->y(0).front(), delta); + TS_ASSERT_DELTA(0.0022329, outputWS->y(0)[middle_index], delta); + TS_ASSERT_DELTA(0.00024214, outputWS->y(0).back(), delta); } void test_Workspace_Beam_Size_Set() { @@ -157,11 +160,12 @@ public: auto outputWS = runAlgorithm(wsProps); verifyDimensions(wsProps, outputWS); - const double delta(1e-08); + const double delta(1e-05); const size_t middle_index(4); - TS_ASSERT_DELTA(0.0343979777, outputWS->y(0).front(), delta); - TS_ASSERT_DELTA(0.0437048479, outputWS->y(0)[middle_index], delta); - TS_ASSERT_DELTA(0.0433649673, outputWS->y(0).back(), delta); + + TS_ASSERT_DELTA(0.0045478, outputWS->y(0).front(), delta); + TS_ASSERT_DELTA(0.00036224, outputWS->y(0)[middle_index], delta); + TS_ASSERT_DELTA(6.5735e-05, outputWS->y(0).back(), delta); } //--------------------------------------------------------------------------- diff --git a/Framework/Geometry/inc/MantidGeometry/Objects/BoundingBox.h b/Framework/Geometry/inc/MantidGeometry/Objects/BoundingBox.h index 1e5d22d1395d8d52fdebca3cd913b28c67b1457c..08736f79496362b1a7a099ed83902eb10108a158 100644 --- a/Framework/Geometry/inc/MantidGeometry/Objects/BoundingBox.h +++ b/Framework/Geometry/inc/MantidGeometry/Objects/BoundingBox.h @@ -222,7 +222,8 @@ typedef boost::shared_ptr<BoundingBox> BoundingBox_sptr; typedef boost::shared_ptr<const BoundingBox> BoundingBox_const_sptr; /// Print out the bounding box values to a stream. -std::ostream &operator<<(std::ostream &os, const BoundingBox &box); +MANTID_GEOMETRY_DLL std::ostream &operator<<(std::ostream &os, + const BoundingBox &box); } } diff --git a/docs/source/algorithms/MonteCarloAbsorption-v1.rst b/docs/source/algorithms/MonteCarloAbsorption-v1.rst index 807f690481aab08ebb0b84e8c939fcebeb867326..b11610f35fcd3514edf809f3ff209ab70ecf7eb7 100644 --- a/docs/source/algorithms/MonteCarloAbsorption-v1.rst +++ b/docs/source/algorithms/MonteCarloAbsorption-v1.rst @@ -51,7 +51,8 @@ The algorithm proceeds as follows. For each spectrum: * for each event in `NEvents`: - - generate a random point on the beam face define by the input height & width + - generate a random point on the beam face defined by the input height & width. If the point is outside of the + area defined by the face of the sample then it is pulled to the boundary of this area - assume the neutron travels in the direction defined by the `samplePos - srcPos` and define a `Track` @@ -89,7 +90,8 @@ Usage data = ConvertUnits(data, Target="Wavelength") # Default up axis is Y SetSample(data, Geometry={'Shape': 'Cylinder', 'Height': 5.0, 'Radius': 1.0, - 'Center': [0.0,0.0,0.0]}, Material={'ChemicalFormula': '(Li7)2-C-H4-N-Cl6'}) + 'Center': [0.0,0.0,0.0]}, + Material={'ChemicalFormula': '(Li7)2-C-H4-N-Cl6', 'SampleNumberDensity': 0.07}) # Simulating every data point can be slow. Use a smaller set and interpolate abscor = MonteCarloAbsorption(data, NumberOfWavelengthPoints=50) corrected = data/abscor @@ -102,7 +104,8 @@ Usage data = ConvertUnits(data, Target="Wavelength") # Default up axis is Y SetSample(data, Geometry={'Shape': 'Cylinder', 'Height': 5.0, 'Radius': 1.0, - 'Center': [0.0,0.0,0.0]}, Material={'ChemicalFormula': '(Li7)2-C-H4-N-Cl6'}) + 'Center': [0.0,0.0,0.0]}, + Material={'ChemicalFormula': '(Li7)2-C-H4-N-Cl6', 'SampleNumberDensity': 0.07}) SetBeam(data, Geometry={'Shape': 'Slit', 'Width': 0.8, 'Height': 1.0}) # Simulating every data point can be slow. Use a smaller set and interpolate abscor = MonteCarloAbsorption(data, NumberOfWavelengthPoints=50) @@ -135,7 +138,7 @@ default facility and instrument respectively. The definition can be found at # we just define the height SetSample(data, Environment={'Name': 'CRYO-01', 'Container': '8mm'}, Geometry={'Height': 4.0}, - Material={'ChemicalFormula': '(Li7)2-C-H4-N-Cl6'}) + Material={'ChemicalFormula': '(Li7)2-C-H4-N-Cl6', 'SampleNumberDensity': 0.07}) # Simulating every data point can be slow. Use a smaller set and interpolate abscor = MonteCarloAbsorption(data, NumberOfWavelengthPoints=30) corrected = data/abscor diff --git a/docs/source/release/v3.8.0/framework.rst b/docs/source/release/v3.8.0/framework.rst index 318a69b01a680e77076f6065dd2d1f9929d36649..e974d487243957cb8bae3725d5d5992b872d1d7e 100644 --- a/docs/source/release/v3.8.0/framework.rst +++ b/docs/source/release/v3.8.0/framework.rst @@ -77,6 +77,9 @@ Improved - :ref:`SetSample <algm-SetSample>`: Fixed a bug with interpreting the `Center` attribute for cylinders/annuli +- :ref:`MonteCarloAbsorption <algm-MonteCarloAbsorption>` had a bug in cases where the beam was larger than the + sample, which lead to the attenuation factor being too high. This has been fixed. + - :ref:`ConvertUnits <algm-ConvertUnits>` now has the option to take a workspace with Points as input. A property has been added that will make the algorithm convert the workspace to Bins automatically. The output space will be converted back to Points.