diff --git a/Framework/Algorithms/inc/MantidAlgorithms/MonteCarloAbsorption.h b/Framework/Algorithms/inc/MantidAlgorithms/MonteCarloAbsorption.h index c5fb1581c29308bea8940c7cc7358c15d7047115..179ca161b01bc2ec2207527a7bee08c921f96728 100644 --- a/Framework/Algorithms/inc/MantidAlgorithms/MonteCarloAbsorption.h +++ b/Framework/Algorithms/inc/MantidAlgorithms/MonteCarloAbsorption.h @@ -64,11 +64,10 @@ private: void exec() override; std::map<std::string, std::string> validateInputs() override; - API::MatrixWorkspace_uptr - doSimulation(const API::MatrixWorkspace &inputWS, const size_t nevents, - int nlambda, const int seed, - const InterpolationOption &interpolateOpt, - const bool useSparseInstrument); + API::MatrixWorkspace_uptr doSimulation( + const API::MatrixWorkspace &inputWS, const size_t nevents, int nlambda, + const int seed, const InterpolationOption &interpolateOpt, + const bool useSparseInstrument, const size_t maxScatterPtAttempts); API::MatrixWorkspace_uptr createOutputWorkspace(const API::MatrixWorkspace &inputWS) const; std::unique_ptr<IBeamProfile> diff --git a/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/MCAbsorptionStrategy.h b/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/MCAbsorptionStrategy.h index d1c86cc11950b55c2eac4508799df003f263f878..c1db0c3b0aa71de6fdc4235e7c1ffb1dc1ba2c27 100644 --- a/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/MCAbsorptionStrategy.h +++ b/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/MCAbsorptionStrategy.h @@ -49,7 +49,8 @@ class IBeamProfile; class MANTID_ALGORITHMS_DLL MCAbsorptionStrategy { public: MCAbsorptionStrategy(const IBeamProfile &beamProfile, - const API::Sample &sample, size_t nevents); + const API::Sample &sample, size_t nevents, + size_t maxScatterPtAttempts); std::tuple<double, double> calculate(Kernel::PseudoRandomNumberGenerator &rng, const Kernel::V3D &finalPos, double lambdaBefore, @@ -59,6 +60,7 @@ private: const IBeamProfile &m_beamProfile; const MCInteractionVolume m_scatterVol; const size_t m_nevents; + const size_t m_maxScatterAttempts; const double m_error; }; diff --git a/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/MCInteractionVolume.h b/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/MCInteractionVolume.h index 05533afd2e1011eebe93cf44be17e12fbeef6a4a..651398733d9ff5b81e2065fda92c6551d91ee72b 100644 --- a/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/MCInteractionVolume.h +++ b/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/MCInteractionVolume.h @@ -48,7 +48,8 @@ class IBeamProfile; class MANTID_ALGORITHMS_DLL MCInteractionVolume { public: MCInteractionVolume(const API::Sample &sample, - const Geometry::BoundingBox &activeRegion); + const Geometry::BoundingBox &activeRegion, + const size_t maxScatterAttempts = 5000); // No creation from temporaries as we store a reference to the object in // the sample MCInteractionVolume(const API::Sample &&sample, @@ -64,6 +65,7 @@ private: const Geometry::Object &m_sample; const Geometry::SampleEnvironment *m_env; const Geometry::BoundingBox m_activeRegion; + const size_t m_maxScatterAttempts; }; } // namespace Algorithms diff --git a/Framework/Algorithms/src/MonteCarloAbsorption.cpp b/Framework/Algorithms/src/MonteCarloAbsorption.cpp index 3251cc82c748b20dc9aaba475d9022b470cdea26..6e78629ef369fac809e2e01b9ca73a2987bee39e 100644 --- a/Framework/Algorithms/src/MonteCarloAbsorption.cpp +++ b/Framework/Algorithms/src/MonteCarloAbsorption.cpp @@ -134,6 +134,17 @@ void MonteCarloAbsorption::init() { "NumberOfDetectorColumns", Kernel::make_unique<EnabledWhenProperty>( "SparseInstrument", ePropertyCriterion::IS_NOT_DEFAULT)); + + // Control the number of attempts made to generate a random point in the + // object + declareProperty("MaxScatterPtAttempts", 5000, positiveInt, + "Maximum number of tries made to generate a scattering point " + "within the sample (+ optional container etc). Objects with " + "holes in them, e.g. a thin annulus can cause problems " + "if this number is too low.\n" + "If a scattering point cannot be generated by increasing " + "this value then there is most likely a problem with " + "the sample geometry."); } /** @@ -147,8 +158,10 @@ void MonteCarloAbsorption::exec() { InterpolationOption interpolateOpt; interpolateOpt.set(getPropertyValue("Interpolation")); const bool useSparseInstrument = getProperty("SparseInstrument"); + const int maxScatterPtAttempts = getProperty("MaxScatterPtAttempts"); auto outputWS = doSimulation(*inputWS, static_cast<size_t>(nevents), nlambda, - seed, interpolateOpt, useSparseInstrument); + seed, interpolateOpt, useSparseInstrument, + static_cast<size_t>(maxScatterPtAttempts)); setProperty("OutputWorkspace", std::move(outputWS)); } @@ -179,12 +192,14 @@ std::map<std::string, std::string> MonteCarloAbsorption::validateInputs() { * @param seed Seed value for the random number generator * @param interpolateOpt Method of interpolation to compute unsimulated points * @param useSparseInstrument If true, use sparse instrument in simulation + * @param maxScatterPtAttempts The maximum number of tries to generate a + * scatter point within the object * @return A new workspace containing the correction factors & errors */ MatrixWorkspace_uptr MonteCarloAbsorption::doSimulation( const MatrixWorkspace &inputWS, const size_t nevents, int nlambda, const int seed, const InterpolationOption &interpolateOpt, - const bool useSparseInstrument) { + const bool useSparseInstrument, const size_t maxScatterPtAttempts) { auto outputWS = createOutputWorkspace(inputWS); const auto inputNbins = static_cast<int>(inputWS.blocksize()); if (isEmpty(nlambda) || nlambda > inputNbins) { @@ -223,7 +238,8 @@ MatrixWorkspace_uptr MonteCarloAbsorption::doSimulation( const std::string reportMsg = "Computing corrections"; // Configure strategy - MCAbsorptionStrategy strategy(*beamProfile, inputWS.sample(), nevents); + MCAbsorptionStrategy strategy(*beamProfile, inputWS.sample(), nevents, + maxScatterPtAttempts); const auto &spectrumInfo = simulationWS.spectrumInfo(); diff --git a/Framework/Algorithms/src/SampleCorrections/MCAbsorptionStrategy.cpp b/Framework/Algorithms/src/SampleCorrections/MCAbsorptionStrategy.cpp index f4a73febff75595e268cc3eaf37e0d3bd7368279..d61bf84d50b378812f841c9f0e29ee30128b3cd6 100644 --- a/Framework/Algorithms/src/SampleCorrections/MCAbsorptionStrategy.cpp +++ b/Framework/Algorithms/src/SampleCorrections/MCAbsorptionStrategy.cpp @@ -6,11 +6,6 @@ #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; @@ -21,14 +16,18 @@ namespace Algorithms { * @param beamProfile A reference to the object the beam profile * @param sample A reference to the object defining details of the sample * @param nevents The number of Monte Carlo events used in the simulation + * @param maxScatterPtAttempts The maximum number of tries to generate a random + * point within the object. */ MCAbsorptionStrategy::MCAbsorptionStrategy(const IBeamProfile &beamProfile, const API::Sample &sample, - size_t nevents) + size_t nevents, + size_t maxScatterPtAttempts) : m_beamProfile(beamProfile), m_scatterVol( MCInteractionVolume(sample, beamProfile.defineActiveRegion(sample))), - m_nevents(nevents), m_error(1.0 / std::sqrt(m_nevents)) {} + m_nevents(nevents), m_maxScatterAttempts(maxScatterPtAttempts), + m_error(1.0 / std::sqrt(m_nevents)) {} /** * Compute the correction for a final position of the neutron and wavelengths @@ -59,9 +58,13 @@ MCAbsorptionStrategy::calculate(Kernel::PseudoRandomNumberGenerator &rng, factor += wgt; break; } - if (attempts == MAX_EVENT_ATTEMPTS) { + if (attempts == m_maxScatterAttempts) { throw std::runtime_error("Unable to generate valid track through " - "sample interaction volume."); + "sample interaction volume after " + + std::to_string(m_maxScatterAttempts) + + " attempts. Try increasing the maximum " + "threshold or if this does not help then " + "please check the defined shape."); } } while (true); } diff --git a/Framework/Algorithms/src/SampleCorrections/MCInteractionVolume.cpp b/Framework/Algorithms/src/SampleCorrections/MCInteractionVolume.cpp index 3d96995f6d11a9bc4dc6658ba936372f9207f064..d57b110310c6a4c48f2a108fba72ead00152729b 100644 --- a/Framework/Algorithms/src/SampleCorrections/MCInteractionVolume.cpp +++ b/Framework/Algorithms/src/SampleCorrections/MCInteractionVolume.cpp @@ -14,9 +14,6 @@ namespace Algorithms { namespace { -// Maximum number of attempts to generate a scatter point -constexpr size_t MAX_SCATTER_ATTEMPTS = 500; - /** * Compute the attenuation factor for the given coefficients * @param rho Number density of the sample in \f$\\A^{-3}\f$ @@ -37,11 +34,14 @@ double attenuation(double rho, double sigma, double length) { * @param sample A reference to a sample object that defines a valid shape * & material * @param activeRegion Restrict scattering point sampling to this region + * @param maxScatterAttempts The maximum number of tries to generate a random + * point within the object. [Default=5000] */ MCInteractionVolume::MCInteractionVolume( - const API::Sample &sample, const Geometry::BoundingBox &activeRegion) - : m_sample(sample.getShape()), m_env(nullptr), - m_activeRegion(activeRegion) { + const API::Sample &sample, const Geometry::BoundingBox &activeRegion, + const size_t maxScatterAttempts) + : m_sample(sample.getShape()), m_env(nullptr), m_activeRegion(activeRegion), + m_maxScatterAttempts(maxScatterAttempts) { if (!m_sample.hasValidShape()) { throw std::invalid_argument( "MCInteractionVolume() - Sample shape does not have a valid shape."); @@ -90,10 +90,10 @@ double MCInteractionVolume::calculateAbsorption( V3D scatterPos; if (m_env && (rng.nextValue() > 0.5)) { scatterPos = - m_env->generatePoint(rng, m_activeRegion, MAX_SCATTER_ATTEMPTS); + m_env->generatePoint(rng, m_activeRegion, m_maxScatterAttempts); } else { scatterPos = m_sample.generatePointInObject(rng, m_activeRegion, - MAX_SCATTER_ATTEMPTS); + m_maxScatterAttempts); } auto toStart = startPos - scatterPos; toStart.normalize(); diff --git a/Framework/Algorithms/test/MCAbsorptionStrategyTest.h b/Framework/Algorithms/test/MCAbsorptionStrategyTest.h index c352450a75e2761d2adc9628323145c909371834..6a8da3947062320d8ffa54aeaf6b62fa14f84655 100644 --- a/Framework/Algorithms/test/MCAbsorptionStrategyTest.h +++ b/Framework/Algorithms/test/MCAbsorptionStrategyTest.h @@ -3,9 +3,11 @@ #include <cxxtest/TestSuite.h> -#include "MantidAlgorithms/SampleCorrections/IBeamProfile.h" #include "MantidAlgorithms/SampleCorrections/MCAbsorptionStrategy.h" +#include "MantidAlgorithms/SampleCorrections/RectangularBeamProfile.h" #include "MantidGeometry/Objects/BoundingBox.h" +#include "MantidGeometry/Instrument/ReferenceFrame.h" +#include "MantidKernel/MersenneTwister.h" #include "MantidKernel/WarningSuppressions.h" #include "MonteCarloTesting.h" @@ -20,11 +22,6 @@ public: } static void destroySuite(MCAbsorptionStrategyTest *suite) { delete suite; } - MCAbsorptionStrategyTest() - : m_nevents(10), m_testBeamProfile(), - m_testSample(MonteCarloTesting::createTestSample( - MonteCarloTesting::TestSampleType::SolidSphere)) {} - //---------------------------------------------------------------------------- // Success cases //---------------------------------------------------------------------------- @@ -33,16 +30,23 @@ public: using namespace MonteCarloTesting; using namespace ::testing; - MockRNG rng; - auto mcabsorb = createTestObject(); + auto testSampleSphere = MonteCarloTesting::createTestSample( + MonteCarloTesting::TestSampleType::SolidSphere); + MockBeamProfile testBeamProfile; + EXPECT_CALL(testBeamProfile, defineActiveRegion(_)) + .WillOnce(Return(testSampleSphere.getShape().getBoundingBox())); + const size_t nevents(10), maxTries(100); + MCAbsorptionStrategy mcabsorb(testBeamProfile, testSampleSphere, nevents, + maxTries); // 3 random numbers per event expected + MockRNG rng; EXPECT_CALL(rng, nextValue()) .Times(Exactly(30)) .WillRepeatedly(Return(0.5)); const Mantid::Algorithms::IBeamProfile::Ray testRay = {V3D(-2, 0, 0), V3D(1, 0, 0)}; - EXPECT_CALL(m_testBeamProfile, generatePoint(_, _)) - .Times(Exactly(static_cast<int>(m_nevents))) + EXPECT_CALL(testBeamProfile, generatePoint(_, _)) + .Times(Exactly(static_cast<int>(nevents))) .WillRepeatedly(Return(testRay)); const V3D endPos(0.7, 0.7, 1.4); const double lambdaBefore(2.5), lambdaAfter(3.5); @@ -51,13 +55,35 @@ public: std::tie(factor, error) = mcabsorb.calculate(rng, endPos, lambdaBefore, lambdaAfter); TS_ASSERT_DELTA(0.0043828472, factor, 1e-08); - TS_ASSERT_DELTA(1.0 / std::sqrt(m_nevents), error, 1e-08); + TS_ASSERT_DELTA(1.0 / std::sqrt(nevents), error, 1e-08); } //---------------------------------------------------------------------------- // Failure cases //---------------------------------------------------------------------------- + void test_thin_object_fails_to_generate_point_in_sample() { + using Mantid::Algorithms::RectangularBeamProfile; + using namespace Mantid::Geometry; + using namespace Mantid::Kernel; + using namespace MonteCarloTesting; + using namespace ::testing; + + auto testThinAnnulus = MonteCarloTesting::createTestSample( + MonteCarloTesting::TestSampleType::ThinAnnulus); + RectangularBeamProfile testBeamProfile( + ReferenceFrame(Y, Z, Right, "source"), V3D(), 1, 1); + const size_t nevents(10), maxTries(1); + MCAbsorptionStrategy mcabs(testBeamProfile, testThinAnnulus, nevents, + maxTries); + MersenneTwister rng; + rng.setSeed(1); + const double lambdaBefore(2.5), lambdaAfter(3.5); + const V3D endPos(0.7, 0.7, 1.4); + TS_ASSERT_THROWS(mcabs.calculate(rng, endPos, lambdaBefore, lambdaAfter), + std::runtime_error) + } + private: class MockBeamProfile final : public Mantid::Algorithms::IBeamProfile { public: @@ -72,18 +98,6 @@ private: const Mantid::API::Sample &)); GCC_DIAG_ON_SUGGEST_OVERRIDE }; - - MCAbsorptionStrategy createTestObject() { - using namespace ::testing; - - EXPECT_CALL(m_testBeamProfile, defineActiveRegion(_)) - .WillOnce(Return(m_testSample.getShape().getBoundingBox())); - return MCAbsorptionStrategy(m_testBeamProfile, m_testSample, m_nevents); - } - - const size_t m_nevents; - MockBeamProfile m_testBeamProfile; - Mantid::API::Sample m_testSample; }; #endif /* MANTID_ALGORITHMS_MCABSORPTIONSTRATEGYTEST_H_ */ diff --git a/Framework/Algorithms/test/MCInteractionVolumeTest.h b/Framework/Algorithms/test/MCInteractionVolumeTest.h index fff2c55c0369fb80a7abae932521b504102a7767..5f807049a0a07793d4ae7eafdd6b1a8a93063d90 100644 --- a/Framework/Algorithms/test/MCInteractionVolumeTest.h +++ b/Framework/Algorithms/test/MCInteractionVolumeTest.h @@ -4,6 +4,7 @@ #include <cxxtest/TestSuite.h> #include "MantidAlgorithms/SampleCorrections/MCInteractionVolume.h" +#include "MantidKernel/MersenneTwister.h" #include "MonteCarloTesting.h" #include <gmock/gmock.h> @@ -140,6 +141,26 @@ public: TS_ASSERT_THROWS_NOTHING( MCInteractionVolume mcv(sample, sample.getShape().getBoundingBox())); } + + void test_Throws_If_Point_Cannot_Be_Generated() { + using namespace Mantid::Kernel; + using namespace MonteCarloTesting; + using namespace ::testing; + + // Testing inputs + const V3D startPos(-2.0, 0.0, 0.0), endPos(2.0, 0.0, 0.0); + const double lambdaBefore(2.5), lambdaAfter(3.5); + + auto sample = createTestSample(TestSampleType::ThinAnnulus); + MersenneTwister rng; + rng.setSeed(1); + const size_t maxTries(1); + MCInteractionVolume interactor(sample, sample.getShape().getBoundingBox(), + maxTries); + TS_ASSERT_THROWS(interactor.calculateAbsorption(rng, startPos, endPos, + lambdaBefore, lambdaAfter), + std::runtime_error); + } }; #endif /* MANTID_ALGORITHMS_MCINTERACTIONVOLUMETEST_H_ */ diff --git a/Framework/Algorithms/test/MonteCarloTesting.h b/Framework/Algorithms/test/MonteCarloTesting.h index 249d477724744ade2eb0ee478bbf7d0ef1950acb..b8d8f65416f9b37d1b88e02fe82cf9672ae3a48c 100644 --- a/Framework/Algorithms/test/MonteCarloTesting.h +++ b/Framework/Algorithms/test/MonteCarloTesting.h @@ -41,7 +41,12 @@ public: // ----------------------------------------------------------------------------- // Create test samples // ----------------------------------------------------------------------------- -enum class TestSampleType { SolidSphere, Annulus, SamplePlusContainer }; +enum class TestSampleType { + SolidSphere, + Annulus, + ThinAnnulus, + SamplePlusContainer +}; inline std::string annulusXML(double innerRadius, double outerRadius, double height, @@ -49,7 +54,9 @@ inline std::string annulusXML(double innerRadius, double outerRadius, using Mantid::Kernel::V3D; // Cylinders oriented along up, with origin at centre of cylinder - const V3D centre(0, 0, -0.5 * height); + // Assume upAxis is a unit vector + V3D centre(upAxis); + centre *= -0.5 * height; const std::string inner = ComponentCreationHelper::cappedCylinderXML( innerRadius, height, centre, upAxis, "inner"); const std::string outer = ComponentCreationHelper::cappedCylinderXML( @@ -117,6 +124,8 @@ inline Mantid::API::Sample createTestSample(TestSampleType sampleType) { shape = ComponentCreationHelper::createSphere(0.1); } else if (sampleType == TestSampleType::Annulus) { shape = createAnnulus(0.1, 0.15, 0.15, V3D(0, 0, 1)); + } else if (sampleType == TestSampleType::ThinAnnulus) { + shape = createAnnulus(0.01, 0.0101, 0.4, V3D(0, 1, 0)); } else { throw std::invalid_argument("Unknown testing shape type requested"); } diff --git a/Framework/DataHandling/src/LoadLog.cpp b/Framework/DataHandling/src/LoadLog.cpp index 96e1840d73f7309fd9c3e15b01b8495660373a34..ca1aec58167ac0b5f3857282396ed4b051e6ca90 100644 --- a/Framework/DataHandling/src/LoadLog.cpp +++ b/Framework/DataHandling/src/LoadLog.cpp @@ -209,7 +209,7 @@ void LoadLog::loadThreeColumnLogFile(std::ifstream &logFileStream, } while (Mantid::Kernel::Strings::extractToEOL(logFileStream, str)) { - if (!isDateTimeString(str)) { + if (!isDateTimeString(str) && !str.empty()) { throw std::invalid_argument("File" + logFileName + " is not a standard ISIS log file. Expected " "to be a file starting with DateTime String " @@ -217,8 +217,8 @@ void LoadLog::loadThreeColumnLogFile(std::ifstream &logFileStream, } if (!Kernel::TimeSeriesProperty<double>::isTimeString(str) || - (str[0] == - '#')) { // if the line doesn't start with a time read the next line + (str.empty() || str[0] == '#')) { + // if the line doesn't start with a time read the next line continue; } @@ -230,6 +230,12 @@ void LoadLog::loadThreeColumnLogFile(std::ifstream &logFileStream, line >> blockcolumn; l_kind = classify(blockcolumn); + if (LoadLog::empty == l_kind) { + g_log.warning() << "Failed to parse line in log file: " << timecolumn + << "\t" << blockcolumn; + continue; + } + if (LoadLog::string != l_kind) { throw std::invalid_argument( "ISIS log file contains unrecognised second column entries:" + @@ -404,9 +410,23 @@ LoadLog::kind LoadLog::classify(const std::string &s) const { if (letters.find_first_of(s) != string::npos) { return LoadLog::string; - } else { - return LoadLog::number; } + + const auto isNumber = [](const std::string &str) { + // try and get stold to parse a number out of the string + // if this throws then we don't have a number + try { + // cppcheck-suppress ignoredReturnValue + std::stold(str); + return true; + } catch (const std::invalid_argument &e) { + return false; + } catch (const std::out_of_range &e) { + return false; + } + }; + + return (isNumber(s)) ? LoadLog::number : LoadLog::empty; } /** diff --git a/Framework/DataHandling/test/LoadLogTest.h b/Framework/DataHandling/test/LoadLogTest.h index 675ee09c68706dee549777fd58ca2a2b38ae841f..6b0c4ec43259ff75313537662c478ca154ebf944 100644 --- a/Framework/DataHandling/test/LoadLogTest.h +++ b/Framework/DataHandling/test/LoadLogTest.h @@ -223,6 +223,20 @@ public: TS_ASSERT_EQUALS(tsp->size(), 33); } + void test_ISISTextFile_withRubbishLogFileInput_fails() { + auto ws = WorkspaceFactory::Instance().create("Workspace2D", 1, 1, 1); + + LoadLog alg; + alg.initialize(); + alg.setPropertyValue("Filename", "ENGINX00275776_ICPputlog.txt"); + alg.setProperty("Workspace", ws); + + TS_ASSERT_THROWS_NOTHING(alg.execute()); + + const auto props = ws->run().getProperties(); + TS_ASSERT_EQUALS(props.size(), 2); + }; + void test_SNSTextFile_noNames_fails() { do_test_SNSTextFile("", "", true); } void test_SNSTextFile_tooFewNames_fails() { diff --git a/Framework/PythonInterface/plugins/algorithms/ComputeIncoherentDOS.py b/Framework/PythonInterface/plugins/algorithms/ComputeIncoherentDOS.py index f6fdce2b1ac8a8c593ba3b8d504ae2cf0931fd67..19052612d62ec2e6a0d64ea88cb209b22c2f57c8 100644 --- a/Framework/PythonInterface/plugins/algorithms/ComputeIncoherentDOS.py +++ b/Framework/PythonInterface/plugins/algorithms/ComputeIncoherentDOS.py @@ -9,11 +9,23 @@ from mantid.simpleapi import * def evaluateEbin(Emin, Emax, Ei, strn): - return [eval(estr) for estr in strn.split(',')] + if strn.count(',') != 2: + raise ValueError('EnergyBinning must be a comma separated string with three values.') + try: + out = [eval(estr, None, {'Emax':Emax, 'Emin':Emin, 'Ei':Ei}) for estr in strn.split(',')] + except NameError: + raise ValueError('Only the variables ''Emin'', ''Emax'' or ''Ei'' are allowed in EnergyBinning.') + return out def evaluateQRange(Qmin, Qmax, strn): - return [eval(qstr) for qstr in strn.split(',')] + if strn.count(',') != 1: + raise ValueError('QSumRange must be a comma separated string with two values.') + try: + out = [eval(qstr, None, {'Qmin':Qmin, 'Qmax':Qmax}) for qstr in strn.split(',')] + except NameError: + raise ValueError('Only the variables ''Qmin'' and ''Qmax'' is allowed in QSumRange.') + return out class ComputeIncoherentDOS(PythonAlgorithm): @@ -97,14 +109,8 @@ class ComputeIncoherentDOS(PythonAlgorithm): qq = (qq[1:len(qq)]+qq[0:len(qq)-1])/2 en = (en[1:len(en)]+en[0:len(en)-1])/2 - # Checks qrange is valid - if QSumRange.count(',') != 1: - raise ValueError('QSumRange must be a comma separated string with two values.') - try: - # Do this in a member function to make sure no other variables can be evaluated other than Qmin and Qmax. - dq = evaluateQRange(min(qq), max(qq), QSumRange) - except NameError: - raise ValueError('Only the variables ''Qmin'' and ''Qmax'' is allowed in QSumRange.') + # Checks qrange is valid. Do it in a member function so no variables can be evaluated other than Qmin and Qmax. + dq = evaluateQRange(min(qq), max(qq), QSumRange) # Gets meV to cm^-1 conversion mev2cm = (constants.elementary_charge / 1000) / (constants.h * constants.c * 100) @@ -118,21 +124,16 @@ class ComputeIncoherentDOS(PythonAlgorithm): en = en / mev2cm input_en_in_meV = 0 - # Checks energy bins are ok. - if EnergyBinning.count(',') != 2: - raise ValueError('EnergyBinning must be a comma separated string with three values.') + # Gets the incident energy from the workspace - either if it is set as an attribute or from the energy axis. try: ei = inws.getEFixed(1) except RuntimeError: ei = max(en) - try: - # Do this in a function to make sure no other variables can be evaluated other than Emin, Emax and Ei. - if not input_en_in_meV: - dosebin = evaluateEbin(min(en*mev2cm), max(en*mev2cm), ei*mev2cm, EnergyBinning) - else: - dosebin = evaluateEbin(min(en), max(en), ei, EnergyBinning) - except NameError: - raise ValueError('Only the variables ''Emin'', ''Emax'' or ''Ei'' are allowed in EnergyBinning.') + # Checks energy bins are ok. Do it in a function so no variables can be evaluated except Emin, Emax and Ei. + if not input_en_in_meV: + dosebin = evaluateEbin(min(en*mev2cm), max(en*mev2cm), ei*mev2cm, EnergyBinning) + else: + dosebin = evaluateEbin(min(en), max(en), ei, EnergyBinning) # Extracts the intensity (y) and errors (e) from inws. y = inws.extractY() diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt b/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt index 7e5a5a4768d2de524e592e04f3b91021ec036109..4a0b0cca90f7a9b025925df4509c63069979d38b 100644 --- a/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt +++ b/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt @@ -16,6 +16,7 @@ set ( TEST_PY_FILES ConjoinSpectraTest.py CompareSampleLogsTest.py ComputeCalibrationCoefVanTest.py + ComputeIncoherentDOSTest.py CorrectLogTimesTest.py CreateLeBailFitInputTest.py CorrectTOFTest.py diff --git a/Testing/Data/UnitTest/ENGINX00275776_ICPputlog.txt.md5 b/Testing/Data/UnitTest/ENGINX00275776_ICPputlog.txt.md5 new file mode 100644 index 0000000000000000000000000000000000000000..c75683223a57290e05c8df2ca0f2e4e841b62f3a --- /dev/null +++ b/Testing/Data/UnitTest/ENGINX00275776_ICPputlog.txt.md5 @@ -0,0 +1 @@ +ade333d2cf33b2432b425ecb6288a3ff diff --git a/buildconfig/Jenkins/buildscript b/buildconfig/Jenkins/buildscript index d0b9e959b0ee2ec95a02623b23f5f58dc2461d9d..4391de0553b4020d7bc98ee35a4e851a231f5afa 100755 --- a/buildconfig/Jenkins/buildscript +++ b/buildconfig/Jenkins/buildscript @@ -95,16 +95,6 @@ if [[ "$CLEANBUILD" == true ]]; then fi if [ -d $BUILD_DIR ]; then rm -rf $BUILD_DIR/bin $BUILD_DIR/ExternalData - ############################################################################### - # Temporary fix to clean build directories while - # https://github.com/mantidproject/mantid/pull/20617 is pushed through - ############################################################################### - _main_generator=$(grep "CMAKE_GENERATOR:INTERNAL" ${BUILD_DIR}/CMakeCache.txt) - _eigen_generator=$(grep "CMAKE_GENERATOR:INTERNAL" ${BUILD_DIR}/eigen-download/CMakeCache.txt) - if [ "${_main_generator}" != "${_eigen_generator}" ]; then - echo "External project generator mismatch. Cleaning external projects" - CLEAN_EXTERNAL_PROJECTS=true - fi if [[ -n ${CLEAN_EXTERNAL_PROJECTS} && "${CLEAN_EXTERNAL_PROJECTS}" == true ]]; then rm -rf $BUILD_DIR/eigen-* rm -rf $BUILD_DIR/googletest-* diff --git a/buildconfig/Jenkins/buildscript.bat b/buildconfig/Jenkins/buildscript.bat index 83d48ac6aae02ee3030069925258b227a2a3e990..789b139e2701f947bf3c7fe42f3e65357b9cd6c8 100755 --- a/buildconfig/Jenkins/buildscript.bat +++ b/buildconfig/Jenkins/buildscript.bat @@ -80,12 +80,6 @@ if "!CLEANBUILD!" == "yes" ( if EXIST %BUILD_DIR% ( rmdir /S /Q %BUILD_DIR%\bin %BUILD_DIR%\ExternalData - call "%GREP_EXE%" CMAKE_GENERATOR:INTERNAL %BUILD_DIR%\eigen-download\CMakeCache.txt > eigen_generator.log - call "%GREP_EXE%" "%CM_GENERATOR%" eigen_generator.log - if ERRORLEVEL 1 ( - echo External project generator mismatch. Cleaning external projects - set CLEAN_EXTERNAL_PROJECTS=true - ) if "!CLEAN_EXTERNAL_PROJECTS!" == "true" ( rmdir /S /Q %BUILD_DIR%\eigen-download %BUILD_DIR%\eigen-src rmdir /S /Q %BUILD_DIR%\googletest-download %BUILD_DIR%\googletest-src diff --git a/docs/source/release/v3.11.0/framework.rst b/docs/source/release/v3.11.0/framework.rst index 31ec178d4b5a790250e8b16c7cfb891b2524b229..706b00ba8e4b38d2aa73427361fae0c8411c0a75 100644 --- a/docs/source/release/v3.11.0/framework.rst +++ b/docs/source/release/v3.11.0/framework.rst @@ -46,7 +46,11 @@ Improved - :ref:`IntegreatePeaksMD <algm-IntegratePeaksMD-v2>` makes the culling of the top one percent of the background events optional. - :ref:`Load <algm-Load-v1>` now supports use of tilde in file paths in Python, for example Load(Filename="~/data/test.nxs", ...) - :ref:`LoadBBY <algm-LoadBBY-v1>` is now better at handling sample information. -- :ref:`algm-MonteCarloAbsorption` now supports approximating the input instrument with a sparse grid of detectors enabling quick simulation of huge pixel arrays. Also, the NumberOfWavelengthPoints input property is now validated more rigorously. +- :ref:`MonteCarloAbsorption <algm-MonteCarloAbsorption-v1>` has had several improvements: + + * it now supports approximating the input instrument with a sparse grid of detectors enabling quick simulation of huge pixel arrays + * the NumberOfWavelengthPoints input property is now validated more rigorously + * a new MaxScatterPtAttempts input has been added to control how many tries are made to generate a random point in the object. Useful for cases such as thin annuli that require a higher number of tries. The previous version was hard coded internally. - :ref:`SaveGSS <algm-SaveGSS-v1>` now supports saving in the legacy GSAS ALT format. This is useful for older tools however the default format FXYE should be used whenever possible. - :ref:`SaveMDWorkspaceToVTK <algm-SaveMDWorkspaceToVTK-v1>` and :ref:`LoadVTK <algm-LoadVTK-v1>` algorithms are now accessible from python. - :ref:`MergeRuns <algm-MergeRuns-v1>` will now merge workspaces with detector scans. diff --git a/docs/source/release/v3.11.0/muon.rst b/docs/source/release/v3.11.0/muon.rst index 208e731721f65344c0f82d31426aad7287848b2d..1a5e381f153be3f3c0b1b2247dbbd0bc900d6d8c 100644 --- a/docs/source/release/v3.11.0/muon.rst +++ b/docs/source/release/v3.11.0/muon.rst @@ -24,6 +24,7 @@ Fit Functions Bug Fixes --------- - Mantid would crash when multiple period data was used in single fits (in muon analysis). This has been fixed. +- Fixed crash when loading data into MuonAnalysis after using Add/Remove curves. | diff --git a/qt/scientific_interfaces/Muon/MuonAnalysis.cpp b/qt/scientific_interfaces/Muon/MuonAnalysis.cpp index 9b28c77e54de823e9d90dd297753824d26e654ce..75be0f243f653879a3dc3b5107d909827a349e82 100644 --- a/qt/scientific_interfaces/Muon/MuonAnalysis.cpp +++ b/qt/scientific_interfaces/Muon/MuonAnalysis.cpp @@ -1701,7 +1701,7 @@ void MuonAnalysis::plotSpectrum(const QString &wsName, bool logScale) { s << " layer = window.activeLayer()"; s << " if layer is not None:"; s << " kept_fits = 0"; - s << " for i in range(layer.numCurves() - 1, -1, -1):"; // reversed + s << " for i in range(layer.numCurves() - 1, 0, -1):"; // reversed s << " title = layer.curveTitle(i)"; s << " if title == \"CompositeFunction\":"; s << " continue"; // keep all guesses @@ -1764,8 +1764,15 @@ void MuonAnalysis::plotSpectrum(const QString &wsName, bool logScale) { // Plot the data! s << "win = get_window('%WSNAME%', '%PREV%', %USEPREV%)"; s << "if %FITSTOKEEP% != -1:"; + // leave the 0th layer -> layer is not empty s << " remove_data(win, %FITSTOKEEP%)"; s << "g = plot_data('%WSNAME%', %ERRORS%, %CONNECT%, win)"; + // if there is more than one layer delete the oldest one manually + s << "if %FITSTOKEEP% != -1:"; + s << " layer = win.activeLayer()"; + s << " if layer.numCurves()>1:"; + s << " layer.removeCurve(0)"; + s << "format_graph(g, '%WSNAME%', %LOGSCALE%, %YAUTO%, '%YMIN%', '%YMAX%')"; QString pyS;