diff --git a/Framework/API/inc/MantidAPI/WorkspaceGroup.h b/Framework/API/inc/MantidAPI/WorkspaceGroup.h index 057f4035f52281b6fcfd8eb9ffa7ffabce5ed371..2d118a1b691de8fda18b48205c0b38ebc2982b10 100644 --- a/Framework/API/inc/MantidAPI/WorkspaceGroup.h +++ b/Framework/API/inc/MantidAPI/WorkspaceGroup.h @@ -67,7 +67,7 @@ public: /// Sort the internal data structure according to member name void sortMembersByName(); /// Adds a workspace to the group. - void addWorkspace(Workspace_sptr workspace); + void addWorkspace(const Workspace_sptr &workspace); /// Return the number of entries within the group int getNumberOfEntries() const { return static_cast<int>(this->size()); } /// Return the size of the group, so it is more like a container @@ -75,7 +75,7 @@ public: /// Return the ith workspace Workspace_sptr getItem(const size_t index) const; /// Return the workspace by name - Workspace_sptr getItem(const std::string wsName) const; + Workspace_sptr getItem(const std::string &wsName) const; /// Return all workspaces in the group as one call for thread safety std::vector<Workspace_sptr> getAllItems() const; /// Remove a workspace from the group diff --git a/Framework/API/src/WorkspaceGroup.cpp b/Framework/API/src/WorkspaceGroup.cpp index 0bdfe8d71a9d215e3f69c863c6516e4be4e84237..54b99597b2ff58793784210e4d3ad0684d49079a 100644 --- a/Framework/API/src/WorkspaceGroup.cpp +++ b/Framework/API/src/WorkspaceGroup.cpp @@ -103,7 +103,7 @@ void WorkspaceGroup::sortMembersByName() { * @param workspace :: A shared pointer to a workspace to add. If the workspace * already exists give a warning. */ -void WorkspaceGroup::addWorkspace(Workspace_sptr workspace) { +void WorkspaceGroup::addWorkspace(const Workspace_sptr &workspace) { std::lock_guard<std::recursive_mutex> _lock(m_mutex); // check it's not there already auto it = std::find(m_workspaces.begin(), m_workspaces.end(), workspace); @@ -111,7 +111,6 @@ void WorkspaceGroup::addWorkspace(Workspace_sptr workspace) { m_workspaces.push_back(workspace); } else { g_log.warning() << "Workspace already exists in a WorkspaceGroup\n"; - ; } } @@ -157,6 +156,7 @@ void WorkspaceGroup::reportMembers(std::set<Workspace_sptr> &memberList) const { std::vector<std::string> WorkspaceGroup::getNames() const { std::lock_guard<std::recursive_mutex> _lock(m_mutex); std::vector<std::string> out; + out.reserve(m_workspaces.size()); for (const auto &workspace : m_workspaces) { out.push_back(workspace->getName()); } @@ -185,11 +185,12 @@ Workspace_sptr WorkspaceGroup::getItem(const size_t index) const { * @throws an out_of_range error if the workspace's name not contained in the * group's list of workspace names */ -Workspace_sptr WorkspaceGroup::getItem(const std::string wsName) const { +Workspace_sptr WorkspaceGroup::getItem(const std::string &wsName) const { std::lock_guard<std::recursive_mutex> _lock(m_mutex); for (const auto &workspace : m_workspaces) { - if (workspace->getName() == wsName) + if (workspace->getName() == wsName) { return workspace; + } } throw std::out_of_range("Workspace " + wsName + " not contained in the group"); diff --git a/Framework/Algorithms/inc/MantidAlgorithms/GroupWorkspaces.h b/Framework/Algorithms/inc/MantidAlgorithms/GroupWorkspaces.h index a01ee0d8441eec7f1bf73bcf50c563a06343806d..347a7d7b8232555c8cbc7f28fe4cef62dca1eab7 100644 --- a/Framework/Algorithms/inc/MantidAlgorithms/GroupWorkspaces.h +++ b/Framework/Algorithms/inc/MantidAlgorithms/GroupWorkspaces.h @@ -57,6 +57,8 @@ public: return "Transforms\\Grouping;Utility\\Workspaces"; } + std::map<std::string, std::string> validateInputs() override; + protected: Parallel::ExecutionMode getParallelExecutionMode( const std::map<std::string, Parallel::StorageMode> &storageModes) @@ -72,7 +74,8 @@ private: /// Add a workspace to the new group, checking for a WorkspaceGroup and /// unrolling it void addToGroup(const API::Workspace_sptr &workspace); - + /// Use a glob pattern to select workspaces in the ADS + void addToGroup(const std::string &globExpression); /// A pointer to the new group API::WorkspaceGroup_sptr m_group; }; diff --git a/Framework/Algorithms/src/GroupWorkspaces.cpp b/Framework/Algorithms/src/GroupWorkspaces.cpp index 6936b02aaf8ffb201e56f6beee4463e81746a1b7..a084df52cf583c775b5993147f1969213aca2431 100644 --- a/Framework/Algorithms/src/GroupWorkspaces.cpp +++ b/Framework/Algorithms/src/GroupWorkspaces.cpp @@ -5,6 +5,8 @@ #include "MantidKernel/ArrayProperty.h" #include "MantidParallel/Communicator.h" +#include "Poco/Glob.h" + namespace Mantid { namespace Algorithms { @@ -16,9 +18,13 @@ using namespace Kernel; /// Initialisation method void GroupWorkspaces::init() { - declareProperty(Kernel::make_unique<ArrayProperty<std::string>>( - "InputWorkspaces", boost::make_shared<ADSValidator>()), - "Names of the Input Workspaces to Group"); + declareProperty( + Kernel::make_unique<ArrayProperty<std::string>>( + "InputWorkspaces", boost::make_shared<ADSValidator>(true, true)), + "Names of the Input Workspaces to Group"); + declareProperty( + std::make_unique<PropertyWithValue<std::string>>("GlobExpression", ""), + "Add all Workspaces that match Glob expression to Group"); declareProperty( make_unique<WorkspaceProperty<WorkspaceGroup>>("OutputWorkspace", "", Direction::Output), @@ -32,15 +38,79 @@ void GroupWorkspaces::exec() { const std::vector<std::string> inputWorkspaces = getProperty("InputWorkspaces"); + const std::string globExpression = getProperty("GlobExpression"); + // Clear WorkspaceGroup in case algorithm instance is reused. m_group = nullptr; - addToGroup(inputWorkspaces); + if (!inputWorkspaces.empty()) + addToGroup(inputWorkspaces); + if (!globExpression.empty()) + addToGroup(globExpression); + if ((m_group == nullptr) || m_group->isEmpty()) + throw std::invalid_argument( + "Glob pattern " + globExpression + + " does not match any workspace names in the ADS."); setProperty("OutputWorkspace", m_group); auto ¬ifier = API::AnalysisDataService::Instance().notificationCenter; notifier.postNotification(new WorkspacesGroupedNotification(inputWorkspaces)); } +std::map<std::string, std::string> GroupWorkspaces::validateInputs() { + std::map<std::string, std::string> results; + const std::vector<std::string> inputWorkspaces = + getProperty("InputWorkspaces"); + std::string globExpression = getProperty("GlobExpression"); + + for (auto it = globExpression.begin(); it != globExpression.end(); ++it) { + if (*it == '\\') { + it = globExpression.erase(it, it + 2); + } + } + + if (inputWorkspaces.empty() && globExpression.empty()) { + results["InputWorkspaces"] = + "No InputWorkspace names specified. Rerun with a list of workspaces " + "names or a glob expression"; + return results; + } + + // ADSValidator already checks names in inputWorkspaces + + if (!globExpression.empty()) { + // This is only a sanity check. If may fail to detect subtle errors in + // complex expressions. + if (globExpression.find_first_of("*?[]") == std::string::npos) { + results["GlobExpression"] = "Expression is expected to contain one or " + "more of the following characters: *?["; + return results; + } + if (std::count(globExpression.cbegin(), globExpression.cend(), '[') != + std::count(globExpression.cbegin(), globExpression.cend(), ']')) { + results["GlobExpression"] = "Expression has a mismatched number of []"; + return results; + } + } + return results; +} + +/** + * Add a list of names to the new group + * @param globExpression glob pattern for selecting names from the ADS + */ +void GroupWorkspaces::addToGroup(const std::string &globExpression) { + + Poco::Glob glob(globExpression); + + AnalysisDataServiceImpl &ads = AnalysisDataService::Instance(); + const auto names = ads.topLevelItems(); + for (const auto &name : names) { + if (glob.match(name.first)) { + addToGroup(name.second); + } + } +} + /** * Add a list of names to the new group * @param names The list of names to add from the ADS @@ -68,9 +138,10 @@ void GroupWorkspaces::addToGroup(const API::Workspace_sptr &workspace) { if (!m_group) m_group = boost::make_shared<WorkspaceGroup>(workspace->storageMode()); else if (communicator().size() != 1 && - m_group->storageMode() != workspace->storageMode()) + m_group->storageMode() != workspace->storageMode()) { throw std::runtime_error( "WorkspaceGroup with mixed Parallel::Storage mode is not supported."); + } m_group->addWorkspace(workspace); } } diff --git a/Framework/Algorithms/test/GroupWorkspacesTest.h b/Framework/Algorithms/test/GroupWorkspacesTest.h index 4d92f2c6bda2b9f2f985cfaa5be94f58c4dac156..0eba9dbd7cea9b945314df5c15f0d049fec7941a 100644 --- a/Framework/Algorithms/test/GroupWorkspacesTest.h +++ b/Framework/Algorithms/test/GroupWorkspacesTest.h @@ -7,6 +7,8 @@ #include "MantidTestHelpers/WorkspaceCreationHelper.h" #include <cxxtest/TestSuite.h> +#include <algorithm> + class GroupWorkspacesTest : public CxxTest::TestSuite { public: // This pair of boilerplate methods prevent the suite being created statically @@ -39,14 +41,17 @@ public: TS_ASSERT(alg.isInitialized()); const auto &props = alg.getProperties(); - TS_ASSERT_EQUALS(props.size(), 2); + TS_ASSERT_EQUALS(props.size(), 3); TS_ASSERT_EQUALS(props[0]->name(), "InputWorkspaces"); TS_ASSERT(props[0]->isDefault()); - TS_ASSERT_EQUALS(props[1]->name(), "OutputWorkspace"); + TS_ASSERT_EQUALS(props[1]->name(), "GlobExpression"); TS_ASSERT(props[1]->isDefault()); - TS_ASSERT(dynamic_cast<WorkspaceProperty<WorkspaceGroup> *>(props[1])); + + TS_ASSERT_EQUALS(props[2]->name(), "OutputWorkspace"); + TS_ASSERT(props[2]->isDefault()); + TS_ASSERT(dynamic_cast<WorkspaceProperty<WorkspaceGroup> *>(props[2])); } void test_Exec_With_Single_Workspace_Succeeds() { @@ -171,6 +176,123 @@ public: removeFromADS(groupName, inputs); } + void test_GlobExpression_Star_Succeeds() { + std::vector<std::string> inputs{"test_name_1", "test_name_2", + "test_name_20"}; + addTestMatrixWorkspacesToADS(inputs); + std::string glob{"test_name_*"}; + std::string groupName{"test_name_output"}; + + TS_ASSERT_THROWS_NOTHING(runAlgorithm(glob, groupName)); + + checkGroupExistsWithMembers(groupName, inputs); + + removeFromADS(groupName, inputs); + } + + void test_GlobExpression_Question_Succeeds() { + std::vector<std::string> inputs{"test_name_1", "test_name_2", + "test_name_20"}; + addTestMatrixWorkspacesToADS(inputs); + std::string glob{"test_name_?"}; + std::string groupName{"test_name_output"}; + + TS_ASSERT_THROWS_NOTHING(runAlgorithm(glob, groupName)); + + std::vector<std::string> group_members(inputs.begin(), inputs.begin() + 2); + + checkGroupExistsWithMembers(groupName, group_members); + + removeFromADS(groupName, inputs); + } + + void test_GlobExpression_Brackets_Succeeds() { + + std::vector<std::string> inputs{"test_name_1", "test_name_2", + "test_name_3"}; + addTestMatrixWorkspacesToADS(inputs); + std::string glob{"test_name_[0-2]"}; + std::string groupName{"test_name_output"}; + + TS_ASSERT_THROWS_NOTHING(runAlgorithm(glob, groupName)); + + checkGroupExistsWithMembers(groupName, {inputs[0], inputs[1]}); + + removeFromADS(groupName, inputs); + } + + void test_GlobExpression_Brackets_Succeeds_2() { + + std::vector<std::string> inputs{"test_name_1", "test_name_2", + "test_name_3"}; + addTestMatrixWorkspacesToADS(inputs); + std::string glob{"test_name_[0-3]"}; + std::string groupName{"test_name_output"}; + + TS_ASSERT_THROWS_NOTHING(runAlgorithm(glob, groupName)); + + checkGroupExistsWithMembers(groupName, inputs); + + removeFromADS(groupName, inputs); + } + + void test_GlobExpression_List_And_Glob_Succeeds() { + + std::vector<std::string> inputs{"test_name_1", "test_name_2", + "test_name_3"}; + addTestMatrixWorkspacesToADS(inputs); + std::string glob{"test_name_[0-2]"}; + std::string groupName{"test_name_output"}; + + TS_ASSERT_THROWS_NOTHING(runAlgorithm({inputs[2]}, glob, groupName)); + + checkGroupExistsWithMembers(groupName, inputs); + + removeFromADS(groupName, inputs); + } + + void test_GlobExpression_List_And_Glob_Succeeds_2() { + + std::vector<std::string> inputs{"test_name_1", "test_name_2", + "test_name_3"}; + addTestMatrixWorkspacesToADS(inputs); + std::string glob{"test_name_[0-3]"}; + std::string groupName{"test_name_output"}; + + TS_ASSERT_THROWS_NOTHING(runAlgorithm({inputs[0]}, glob, groupName)); + + checkGroupExistsWithMembers(groupName, inputs); + + removeFromADS(groupName, inputs); + } + + void test_GlobExpression_EscapedCharacter_Succeeds() { + std::vector<std::string> inputs{"test_name_1", "test_?_2", "test_n_3"}; + addTestMatrixWorkspacesToADS(inputs); + std::string glob{"test_\\?_?"}; + std::string groupName{"test_name_output"}; + + TS_ASSERT_THROWS_NOTHING(runAlgorithm(glob, groupName)); + + checkGroupExistsWithMembers(groupName, {inputs[1]}); + + removeFromADS(groupName, inputs); + } + + void test_GlobExpression_EscapedCharacter_Succeeds_2() { + std::vector<std::string> inputs{"test_name_1", "test_name_2", + "test_name_3"}; + addTestMatrixWorkspacesToADS(inputs); + std::string glob{"test_name_[2-3]"}; + std::string groupName{"test_name_output"}; + + TS_ASSERT_THROWS_NOTHING(runAlgorithm({inputs[0]}, glob, groupName)); + + checkGroupExistsWithMembers(groupName, inputs); + + removeFromADS(groupName, inputs); + } + //========================= Failure Cases //=========================================== @@ -195,6 +317,57 @@ public: removeFromADS("", inputs); } + void test_GlobExpression_Mismatched_Brackets_Fails() { + + std::vector<std::string> inputs{"test_name_1", "test_name_2", + "test_name_3"}; + addTestMatrixWorkspacesToADS(inputs); + std::string glob{"test_name_[1-3]]"}; + std::string groupName{"test_name_output"}; + + runAlgorithm(glob, groupName, true); + + TS_ASSERT_EQUALS( + false, + Mantid::API::AnalysisDataService::Instance().doesExist(groupName)); + + removeFromADS("", inputs); + } + + void test_GlobExpression_Fails() { + + std::vector<std::string> inputs{"test_name_1", "test_name_2", + "test_name_3"}; + addTestMatrixWorkspacesToADS(inputs); + std::string glob{"test_name_1"}; + std::string groupName{"test_name_output"}; + + runAlgorithm(glob, groupName, true); + + TS_ASSERT_EQUALS( + false, + Mantid::API::AnalysisDataService::Instance().doesExist(groupName)); + + removeFromADS("", inputs); + } + + void test_GlobExpression_Empty_Output_Fails() { + + std::vector<std::string> inputs{"test_name_1", "test_name_2", + "test_name_3"}; + addTestMatrixWorkspacesToADS(inputs); + std::string glob{"test_name_[!1-3]"}; + std::string groupName{"test_name_output"}; + + runAlgorithm(glob, groupName, true); + + TS_ASSERT_EQUALS( + false, + Mantid::API::AnalysisDataService::Instance().doesExist(groupName)); + + removeFromADS("", inputs); + } + private: void addTestMatrixWorkspacesToADS(const std::vector<std::string> &inputs) { for (const auto &input : inputs) { @@ -236,9 +409,43 @@ private: } } - void - checkGroupExistsWithMembers(const std::string &groupName, - const std::vector<std::string> &expectedMembers) { + void runAlgorithm(const std::string &globExpression, + const std::string &outputWorkspace, + bool errorExpected = false) { + Mantid::Algorithms::GroupWorkspaces alg; + alg.initialize(); + alg.setRethrows(true); + + if (errorExpected) { + alg.setProperty("GlobExpression", globExpression); + alg.setProperty("OutputWorkspace", outputWorkspace); + TS_ASSERT_THROWS_ANYTHING(alg.execute()); + } else { + TS_ASSERT_THROWS_NOTHING( + alg.setProperty("GlobExpression", globExpression)); + TS_ASSERT_THROWS_NOTHING( + alg.setProperty("OutputWorkspace", outputWorkspace)); + alg.execute(); + TS_ASSERT(alg.isExecuted()); + } + } + + void runAlgorithm(const std::vector<std::string> &inputs, + const std::string &globExpression, + const std::string &outputWorkspace) { + Mantid::Algorithms::GroupWorkspaces alg; + alg.initialize(); + alg.setRethrows(true); + + TS_ASSERT_THROWS_NOTHING(alg.setProperty("InputWorkspaces", inputs)); + TS_ASSERT_THROWS_NOTHING(alg.setProperty("GlobExpression", globExpression)); + TS_ASSERT_THROWS_NOTHING( + alg.setProperty("OutputWorkspace", outputWorkspace)); + alg.execute(); + TS_ASSERT(alg.isExecuted()); + } + void checkGroupExistsWithMembers(const std::string &groupName, + std::vector<std::string> expectedMembers) { using namespace Mantid::API; auto &ads = AnalysisDataService::Instance(); @@ -248,6 +455,9 @@ private: std::vector<std::string> grpVec = result->getNames(); TS_ASSERT_EQUALS(expectedMembers.size(), grpVec.size()); + std::sort(expectedMembers.begin(), expectedMembers.end()); + std::sort(grpVec.begin(), grpVec.end()); + if (expectedMembers.size() == grpVec.size()) { for (size_t i = 0; i < expectedMembers.size(); ++i) { TS_ASSERT_EQUALS(expectedMembers[i], grpVec[i]); diff --git a/docs/source/algorithms/GroupWorkspaces-v1.rst b/docs/source/algorithms/GroupWorkspaces-v1.rst index 28ea510b27ea25fa4c0d04e718df638fd0b9c169..ac1d5f14a50eeae216ae02ca0b3c6ad85b4fcc82 100644 --- a/docs/source/algorithms/GroupWorkspaces-v1.rst +++ b/docs/source/algorithms/GroupWorkspaces-v1.rst @@ -10,7 +10,8 @@ Description ----------- This algorithm takes two or more workspaces as input and creates an -output workspace group. +output workspace group. A list of workspaces and a glob pattern may +be specified together. Usage ----- @@ -30,6 +31,15 @@ Usage print('Its first item is {}'.format(group.getItem(0))) print('Its second item is {}'.format(group.getItem(1))) + wrkspc1 = CreateSampleWorkspace() + wrkspc2 = CreateSampleWorkspace() + anotherGroup = GroupWorkspaces(GlobExpression='wrkspc?') + + # Check the result + print('It has {} entries'.format(anotherGroup.getNumberOfEntries())) + print('Its first item is {}'.format(anotherGroup.getItem(0))) + print('Its second item is {}'.format(anotherGroup.getItem(1))) + Output ###### @@ -39,6 +49,9 @@ Output It has 2 entries Its first item is ws1 Its second item is ws2 + It has 2 entries + Its first item is wrkspc1 + Its second item is wrkspc2 .. categories::