diff --git a/Framework/Muon/CMakeLists.txt b/Framework/Muon/CMakeLists.txt index b42f1aa68a14..88ebcbbd7344 100644 --- a/Framework/Muon/CMakeLists.txt +++ b/Framework/Muon/CMakeLists.txt @@ -13,6 +13,7 @@ set(SRC_FILES src/LoadMuonNexus.cpp src/LoadMuonNexus1.cpp src/LoadMuonNexus2.cpp + src/LoadMuonNexus3.cpp src/MuonAlgorithmHelper.cpp src/MuonAsymmetryHelper.cpp src/MuonGroupDetectors.cpp @@ -41,6 +42,7 @@ set(INC_FILES inc/MantidMuon/LoadMuonNexus.h inc/MantidMuon/LoadMuonNexus1.h inc/MantidMuon/LoadMuonNexus2.h + inc/MantidMuon/LoadMuonNexus3.h inc/MantidMuon/EstimateMuonAsymmetryFromCounts.h inc/MantidMuon/MuonAlgorithmHelper.h inc/MantidMuon/MuonAsymmetryHelper.h @@ -69,6 +71,7 @@ set(TEST_FILES LoadAndApplyMuonDetectorGroupingTest.h LoadMuonNexus1Test.h LoadMuonNexus2Test.h + LoadMuonNexus3Test.h MuonAlgorithmHelperTest.h EstimateMuonAsymmetryFromCountsTest.h MuonGroupDetectorsTest.h diff --git a/Framework/Muon/inc/MantidMuon/LoadMuonNexus3.h b/Framework/Muon/inc/MantidMuon/LoadMuonNexus3.h new file mode 100644 index 000000000000..070d48d9e8e7 --- /dev/null +++ b/Framework/Muon/inc/MantidMuon/LoadMuonNexus3.h @@ -0,0 +1,88 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2025 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source, +// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +// SPDX - License - Identifier: GPL - 3.0 + +#pragma once + +//---------------------------------------------------------------------- +// Includes +//---------------------------------------------------------------------- +#include "MantidMuon/DllConfig.h" +#include "MantidMuon/LoadMuonNexus.h" + +#include + +namespace Mantid::Algorithms { + +using ConfFuncPtr = int (*)(const std::string &, const std::shared_ptr &); + +struct AlgDetail { + AlgDetail(const std::string &name, const int version, const ConfFuncPtr &loader, + const Mantid::API::Algorithm_sptr &alg) + : m_name(name), m_version(version), m_confFunc(loader), m_alg(alg) {} + + const std::string m_name; + const int m_version; + const ConfFuncPtr m_confFunc; + const Mantid::API::Algorithm_sptr m_alg; +}; + +/** +Loads an file in NeXus Muon format version 1 and 2 and stores it in a 2D +workspace +(Workspace2D class). LoadMuonNexus is an algorithm and as such inherits +from the Algorithm class, via DataHandlingCommand, and overrides +the init() & exec() methods. + +Required Properties: + + +Optional Properties: (note that these options are not available if reading a +multiperiod file) + +*/ +class MANTID_MUON_DLL LoadMuonNexus3 : public LoadMuonNexus { +public: + LoadMuonNexus3(); + + const std::string summary() const override { + return "The LoadMuonNexus algorithm will read the given NeXus Muon data " + "file Version 1 or 2 and use the results to populate the named " + "workspace. LoadMuonNexus may be invoked by LoadNexus if it is " + "given a NeXus file of this type."; + } + + int version() const override { return 3; } + const std::vector seeAlso() const override { return {"LoadNexus", "LoadMuonNexusV2"}; } + + // Returns 0, as this wrapper version of the algorithm is never to be selected via load. + int confidence(Kernel::NexusDescriptor &) const override { return 0; }; + // Methods to enable testing. + const std::string &getSelectedAlg() const { return m_loadAlgs[m_selectedIndex].m_name; } + int getSelectedVersion() const { return m_loadAlgs[m_selectedIndex].m_version; } + +private: + std::vector m_loadAlgs; + size_t m_selectedIndex; + + void exec() override; + void runSelectedAlg(); + void addAlgToVec(const std::string &name, const int version, const ConfFuncPtr &loader); +}; + +} // namespace Mantid::Algorithms diff --git a/Framework/Muon/src/LoadMuonNexus3.cpp b/Framework/Muon/src/LoadMuonNexus3.cpp new file mode 100644 index 000000000000..735b4511ecc2 --- /dev/null +++ b/Framework/Muon/src/LoadMuonNexus3.cpp @@ -0,0 +1,96 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2025 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source, +// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +// SPDX - License - Identifier: GPL - 3.0 + +#include "MantidMuon/LoadMuonNexus3.h" + +#include "MantidAPI/AlgorithmFactory.h" +#include "MantidAPI/NexusFileLoader.h" +#include "MantidAPI/Progress.h" +#include "MantidAPI/RegisterFileLoader.h" +#include "MantidKernel/Logger.h" +#include "MantidNexus/NexusClasses.h" +#include +#include + +namespace { +const int CONFIDENCE_THRESHOLD{80}; + +int calculateConfidenceHDF5(const std::string &filePath, const std::shared_ptr &alg) { + const auto nexusLoader = std::dynamic_pointer_cast(alg); + int confidence{0}; + if (H5::H5File::isHdf5(filePath)) { + try { + Mantid::Kernel::NexusHDF5Descriptor descriptorHDF5(filePath); + confidence = nexusLoader->confidence(descriptorHDF5); + } catch (std::exception const &e) { + Mantid::Kernel::Logger("LoadMuonNexus3").debug() + << "Error in calculating confidence for: " << nexusLoader->name() << " " << e.what() << '\n'; + } + } + return (confidence >= CONFIDENCE_THRESHOLD) ? confidence : 0; +} + +int calculateConfidence(const std::string &filePath, const std::shared_ptr &alg) { + const auto fileLoader = std::dynamic_pointer_cast>(alg); + Mantid::Kernel::NexusDescriptor descriptor(filePath); + const int confidence = fileLoader->confidence(descriptor); + return (confidence >= CONFIDENCE_THRESHOLD) ? confidence : 0; +} +} // namespace + +namespace Mantid::Algorithms { +// Register the algorithm into the algorithm factory +DECLARE_NEXUS_FILELOADER_ALGORITHM(LoadMuonNexus3) + +/** Executes the right version of the Muon nexus loader + * @throw Exception::FileError If the Nexus file cannot be found/opened + * @throw std::invalid_argument If the optional properties are set to invalid + *values + */ +LoadMuonNexus3::LoadMuonNexus3() : LoadMuonNexus() { + addAlgToVec("LoadMuonNexusV2", 1, &calculateConfidenceHDF5); + addAlgToVec("LoadMuonNexus", 1, &calculateConfidence); + addAlgToVec("LoadMuonNexus", 2, &calculateConfidence); +} + +void LoadMuonNexus3::exec() { + const std::string filePath = getPropertyValue("Filename"); + + int maxConfidenceRes{0}; + for (size_t i = 0; i < m_loadAlgs.size(); i++) { + const int confidenceRes = m_loadAlgs[i].m_confFunc(filePath, m_loadAlgs[i].m_alg); + if (confidenceRes > maxConfidenceRes) { + maxConfidenceRes = confidenceRes; + m_selectedIndex = i; + } + } + + if (!maxConfidenceRes) { + throw Kernel::Exception::FileError("Cannot open the file ", filePath); + } + + runSelectedAlg(); +} + +void LoadMuonNexus3::runSelectedAlg() { + const auto &alg = m_loadAlgs[m_selectedIndex].m_alg; + this->setupAsChildAlgorithm(alg, 0, 1, true); + alg->copyPropertiesFrom(*this); + alg->executeAsChildAlg(); + this->copyPropertiesFrom(*alg); +} + +void LoadMuonNexus3::addAlgToVec(const std::string &name, const int version, const ConfFuncPtr &loader) { + auto &factory = API::AlgorithmFactory::Instance(); + if (factory.exists(name, version)) { + const auto alg = factory.create(name, version); + m_loadAlgs.push_back(AlgDetail(name, version, loader, alg)); + } else { + Mantid::Kernel::Logger("LoadMuonNexus3").debug() + << "Cannot add algorithm: " << name << " v" << version << ". The algorithm is not registered." << '\n'; + } +} +} // namespace Mantid::Algorithms diff --git a/Framework/Muon/test/LoadMuonNexus3Test.h b/Framework/Muon/test/LoadMuonNexus3Test.h new file mode 100644 index 000000000000..263fee71e1d9 --- /dev/null +++ b/Framework/Muon/test/LoadMuonNexus3Test.h @@ -0,0 +1,123 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2025 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source, +// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +// SPDX - License - Identifier: GPL - 3.0 + +#pragma once + +#include + +#include "MantidAPI/AnalysisDataService.h" +#include "MantidAPI/NexusFileLoader.h" +#include "MantidAPI/WorkspaceGroup.h" +#include "MantidDataObjects/Workspace2D.h" +#include "MantidMuon/LoadMuonNexus1.h" +#include "MantidMuon/LoadMuonNexus2.h" +#include "MantidMuon/LoadMuonNexus3.h" + +using namespace Mantid::API; +using namespace Mantid::Kernel; +using namespace Mantid::Algorithms; +using namespace Mantid::DataObjects; + +namespace { +// Mock class for LoadMuonNexusV2 which is in the DataHandling Library +class LoadMuonNexusV2 : public NexusFileLoader { + const std::string name() const override { return "LoadMuonNexusV2"; } + int version() const override { return 1; } + int confidence(NexusHDF5Descriptor &) const override { return 100; } + void execLoader() override {} + const std::string summary() const override { return "mock class"; } + void init() override {} +}; +} // namespace + +class LoadMuonNexus3Test : public CxxTest::TestSuite { +public: + void check_spectra_and_detectors(const MatrixWorkspace_sptr &output) { + + //---------------------------------------------------------------------- + // Tests to check that spectra-detector mapping is done correctly + //---------------------------------------------------------------------- + // Check the total number of elements in the map for HET + TS_ASSERT_EQUALS(output->getNumberHistograms(), 192); + + // Test one to one mapping, for example spectra 6 has only 1 pixel + TS_ASSERT_EQUALS(output->getSpectrum(6).getDetectorIDs().size(), 1); + + auto detectorgroup = output->getSpectrum(99).getDetectorIDs(); + TS_ASSERT_EQUALS(detectorgroup.size(), 1); + TS_ASSERT_EQUALS(*detectorgroup.begin(), 100); + } + + void testExecLoadMuonNexus2() { + LoadMuonNexus3 nxLoad; + nxLoad.initialize(); + + // Now set required filename and output workspace name + std::string inputFile = "argus0026287.nxs"; + nxLoad.setPropertyValue("FileName", inputFile); + + std::string outputSpace = "outer"; + nxLoad.setPropertyValue("OutputWorkspace", outputSpace); + + // Test execute to read file and populate workspace + TS_ASSERT_THROWS_NOTHING(nxLoad.execute()); + TS_ASSERT(nxLoad.isExecuted()); + + // Check output workspace + MatrixWorkspace_sptr output; + output = AnalysisDataService::Instance().retrieveWS(outputSpace); + Workspace2D_sptr output2D = std::dynamic_pointer_cast(output); + + // Perform limited tests on the outwork workspace as this is essentially just a wrapper algorithm. + // subset of tests performed in LoadMuonNexus2Test + check_spectra_and_detectors(output); + + TS_ASSERT(nxLoad.getSelectedAlg() == "LoadMuonNexus"); + TS_ASSERT(nxLoad.getSelectedVersion() == 2); + } + + void testExecLoadMuonNexus1() { + LoadMuonNexus3 nxLoad; + nxLoad.initialize(); + + // Now set required filename and output workspace name + std::string inputFile = "emu00006475.nxs"; + nxLoad.setPropertyValue("FileName", inputFile); + + std::string outputSpace = "outer"; + nxLoad.setPropertyValue("OutputWorkspace", outputSpace); + + // Test execute to read file and populate workspace + TS_ASSERT_THROWS_NOTHING(nxLoad.execute()); + TS_ASSERT(nxLoad.isExecuted()); + + // Check output workspace group + Mantid::API::WorkspaceGroup_sptr output = AnalysisDataService::Instance().retrieveWS(outputSpace); + TS_ASSERT(output->size() == 4); + + TS_ASSERT(nxLoad.getSelectedAlg() == "LoadMuonNexus"); + TS_ASSERT(nxLoad.getSelectedVersion() == 1); + } + + void testExecLoadMuonNexusV2() { + LoadMuonNexus3 nxLoad; + nxLoad.initialize(); + + // Now set required filename and output workspace name + std::string inputFile = "ARGUS00073601.nxs"; + nxLoad.setPropertyValue("FileName", inputFile); + + std::string outputSpace = "outer"; + nxLoad.setPropertyValue("OutputWorkspace", outputSpace); + + // Test execute to read file and populate workspace + TS_ASSERT_THROWS_NOTHING(nxLoad.execute()); + TS_ASSERT(nxLoad.isExecuted()); + + TS_ASSERT(nxLoad.getSelectedAlg() == "LoadMuonNexusV2"); + TS_ASSERT(nxLoad.getSelectedVersion() == 1); + } +}; diff --git a/docs/source/algorithms/LoadMuonNexus-v3.rst b/docs/source/algorithms/LoadMuonNexus-v3.rst new file mode 100644 index 000000000000..c8975f8e4ae2 --- /dev/null +++ b/docs/source/algorithms/LoadMuonNexus-v3.rst @@ -0,0 +1,86 @@ +.. algorithm:: + +.. summary:: + +.. relatedalgorithms:: + +.. properties:: + +Description +----------- + +The algorithm LoadMuonNexus will read a Muon Nexus data file and place +the data into the named workspace. The file name can be an absolute or +relative path and should have the extension .nxs or .NXS. + +v3 of this algoirthm acts as an algorithm selector. Given the input filename, +the confidence of the following loaders will be assessed and the loader with the +highest confidence selected: + +- :ref:`algm-LoadMuonNexus-v1` +- :ref:`algm-LoadMuonNexus-v2` +- :ref:`algm-LoadMuonNexusV2-v1` + +This algorithm has the same input properties as :ref:`algm-LoadMuonNexus-v2`, some +algorithm properties only apply to :ref:`algm-LoadMuonNexus-v1`. + +Please refer to the documentation for the individual algorithms for implementation detail. + +Usage +----- + +.. include:: ../usagedata-note.txt + +**Example - Load ISIS muon MUSR dataset:** + +.. testcode:: LoadMuonNexusOnePeriod + + # Load MUSR dataset + ws = LoadMuonNexus(Filename="MUSR00015189.nxs",EntryNumber=1) + print("Workspace has {} spectra".format(ws[0].getNumberHistograms())) + +Output: + +.. testoutput:: LoadMuonNexusOnePeriod + + Workspace has 64 spectra + +**Example - Load event nexus file with time filtering:** + +.. testcode:: ExLoadMuonNexusSomeSpectra + + # Load some spectra + ws = LoadMuonNexus(Filename="MUSR00015189.nxs",SpectrumMin=5,SpectrumMax=10,EntryNumber=1) + print("Workspace has {} spectra".format(ws[0].getNumberHistograms())) + +Output: + +.. testoutput:: ExLoadMuonNexusSomeSpectra + + Workspace has 6 spectra + +**Example - Load dead times into table:** + +.. testcode:: ExLoadDeadTimeTable + + # Load some spectra + ws = LoadMuonNexus(Filename="emu00006473.nxs",SpectrumMin=5,SpectrumMax=10,DeadTimeTable="deadTimeTable") + tab = mtd['deadTimeTable'] + for i in range(0,tab.rowCount()): + print("{} {:.12f}".format(tab.cell(i,0), tab.cell(i,1))) + +Output: + +.. testoutput:: ExLoadDeadTimeTable + + 5 0.001611122512 + 6 0.002150168177 + 7 0.010217159986 + 8 0.004316862207 + 9 0.007436056621 + 10 0.004211476538 + + +.. categories:: + +.. sourcelink:: diff --git a/docs/source/release/v6.12.0/muon.rst b/docs/source/release/v6.12.0/muon.rst index ae3d7ea69363..e156a491adff 100644 --- a/docs/source/release/v6.12.0/muon.rst +++ b/docs/source/release/v6.12.0/muon.rst @@ -33,8 +33,9 @@ New features Algorithms ---------- -Bugfixes +New features ############ -- The :ref:`LoadMuonNexus v2` algorithm no longer chooses a different Muon loader if appropriate. Users should call the :ref:`Load ` algorithm directly if they want the appropriate loader to be chosen for them. +- The :ref:`LoadMuonNexus v2` algorithm no longer chooses a different Muon loader if appropriate. This functionality has been moved to a new algorithm version, :ref:`LoadMuonNexus v3`. +- Users can also call the :ref:`Load ` algorithm directly which will also select the appropriate loader for them. :ref:`Release 6.12.0 `