Skip to content
Merged
3 changes: 3 additions & 0 deletions Framework/Muon/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -69,6 +71,7 @@ set(TEST_FILES
LoadAndApplyMuonDetectorGroupingTest.h
LoadMuonNexus1Test.h
LoadMuonNexus2Test.h
LoadMuonNexus3Test.h
MuonAlgorithmHelperTest.h
EstimateMuonAsymmetryFromCountsTest.h
MuonGroupDetectorsTest.h
Expand Down
88 changes: 88 additions & 0 deletions Framework/Muon/inc/MantidMuon/LoadMuonNexus3.h
Original file line number Diff line number Diff line change
@@ -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 <vector>

namespace Mantid::Algorithms {

using ConfFuncPtr = int (*)(const std::string &, const std::shared_ptr<Mantid::API::Algorithm> &);

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Loads an file in NeXus Muon format version 1 and 2 and stores it in a 2D
Loads a file in NeXus Muon format version 1 and 2 and stores it in a 2D

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now this I can't abide

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:
<UL>
<LI> Filename - The name of and path to the input NeXus file </LI>
<LI> OutputWorkspace - The name of the workspace in which to store the imported
data
(a multiperiod file will store higher periods in workspaces called
OutputWorkspace_PeriodNo)
[ not yet implemented for NeXus ]</LI>
</UL>

Optional Properties: (note that these options are not available if reading a
multiperiod file)
<UL>
<LI> spectrum_min - The spectrum to start loading from</LI>
<LI> spectrum_max - The spectrum to load to</LI>
<LI> spectrum_list - An ArrayProperty of spectra to load</LI>
<LI> auto_group - Determines whether the spectra are automatically grouped
together based on the groupings in the NeXus file. </LI>
</UL>
*/
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 "
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does Version 1 or 2 reference a file format, or LoadMuonNexus versions? I find this description quite confusing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this references a file format, the naming of the algorithms has caused much confusion. I think the Muon scientists will all understand this, but I'll add in the word format which will hopefully add a bit more clarity for everyone else.

"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<std::string> 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<AlgDetail> 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
96 changes: 96 additions & 0 deletions Framework/Muon/src/LoadMuonNexus3.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Mantid Repository : https://github.com/mantidproject/mantid
//
// Copyright &copy; 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 <H5Cpp.h>
#include <string>

namespace {
const int CONFIDENCE_THRESHOLD{80};

int calculateConfidenceHDF5(const std::string &filePath, const std::shared_ptr<Mantid::API::Algorithm> &alg) {
const auto nexusLoader = std::dynamic_pointer_cast<Mantid::API::NexusFileLoader>(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<Mantid::API::Algorithm> &alg) {
const auto fileLoader = std::dynamic_pointer_cast<Mantid::API::IFileLoader<Mantid::Kernel::NexusDescriptor>>(alg);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know we're pretty certain that fileLoader will not be null, but I think it wouldn't hurt to check.

(Same thing in calculateConfidenceHDF5, I thought it would be caught by the try but it's used in the catch block)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this, I guess it's worth adding in a check in case anyone in the future extends this algorithm with a new alg and assigns the wrong confidence function.

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
123 changes: 123 additions & 0 deletions Framework/Muon/test/LoadMuonNexus3Test.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Mantid Repository : https://github.com/mantidproject/mantid
//
// Copyright &copy; 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 <cxxtest/TestSuite.h>

#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) {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

//----------------------------------------------------------------------
// 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<MatrixWorkspace>(outputSpace);
Workspace2D_sptr output2D = std::dynamic_pointer_cast<Workspace2D>(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<WorkspaceGroup>(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);
}
};
Loading