From 092ced6e2f3ccf778a8878f6b5bf1f634afd1ec6 Mon Sep 17 00:00:00 2001 From: Reiher Research Group Date: Mon, 27 Apr 2020 13:04:40 +0200 Subject: [PATCH] Release 2.0.0 --- CHANGELOG.md | 6 + CMakeLists.txt | 7 +- CONTRIBUTING.md | 32 ++ cmake | 2 +- src/Core/CMakeLists.txt | 10 +- .../Core/BaseClasses/ObjectWithStructure.h | 61 +++ .../Core/BaseClasses/StateHandableObject.h | 72 ++++ src/Core/Core/DerivedModule.h | 400 ++++++++++++++++-- src/Core/Core/Exceptions.h | 10 + src/Core/Core/Interfaces/Calculator.h | 72 ++-- .../Core/Interfaces/CalculatorWithReference.h | 106 +++++ src/Core/Core/Interfaces/MMParametrizer.h | 63 +++ .../Interfaces/WavefunctionOutputGenerator.h | 59 +++ src/Core/Core/Module.h | 53 ++- src/Core/Core/ModuleManager.cpp | 138 ++++-- src/Core/Core/ModuleManager.h | 68 ++- src/Core/Files.cmake | 4 + src/Core/Tests/CMakeLists.txt | 15 + src/Core/Tests/CoreTests.cpp | 45 ++ src/Core/Tests/DummyInterface.h | 32 ++ src/Core/Tests/DummyModels.cpp | 19 + src/Core/Tests/DummyModels.h | 27 ++ src/Core/Tests/SampleModule.cpp | 96 +++++ src/Core/Tests/SampleModule.h | 71 ++++ src/Core/config.cmake.in | 10 +- 25 files changed, 1319 insertions(+), 159 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 src/Core/Core/BaseClasses/ObjectWithStructure.h create mode 100644 src/Core/Core/BaseClasses/StateHandableObject.h create mode 100644 src/Core/Core/Interfaces/CalculatorWithReference.h create mode 100644 src/Core/Core/Interfaces/MMParametrizer.h create mode 100644 src/Core/Core/Interfaces/WavefunctionOutputGenerator.h create mode 100644 src/Core/Tests/CMakeLists.txt create mode 100644 src/Core/Tests/CoreTests.cpp create mode 100644 src/Core/Tests/DummyInterface.h create mode 100644 src/Core/Tests/DummyModels.cpp create mode 100644 src/Core/Tests/DummyModels.h create mode 100644 src/Core/Tests/SampleModule.cpp create mode 100644 src/Core/Tests/SampleModule.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f55f4f..5892ed0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Release 2.0.0 + +- Replace Boost HANA with Boost MPL. Note that this changes all the interfaces; + Core 2.0.0 is therefore not backwards compatible. +- Various bugfixes and improvements. + ## Release 1.0.0 Initial release with all necessary functionality to support Sparrow and ReaDuct. diff --git a/CMakeLists.txt b/CMakeLists.txt index c04a2b9..b51dfb9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.9) # tree must then provide a properly namespaced target with the same name as # your project. project(Core - VERSION 1.0.0 + VERSION 2.0.0 DESCRIPTION "Module management and core interface definitions" ) @@ -15,5 +15,10 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) include(ComponentSetup) scine_setup_component() +# Testing +if(SCINE_BUILD_TESTS) + enable_testing() +endif() + # Subdirectories add_subdirectory(src) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..a117523 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,32 @@ +Contributing to SCINE Core +========================== + +Contribution Process +-------------------- + +The development for this code is done in a private repository maintained by the +Reiher Research Group. GitHub is only used for the official releases. + +If you would like to contribute a larger change, please write to . +For smaller changes, you can create a pull request on GitHub. If we agree with +the changes, a member of the Reiher Research Group will include them in our +development code. Of course, we will give proper acknowledgment for any external +contribution (see below for a list of all contributors). As soon as these changes +are available in an official release, we will close the corresponding pull requests +and/or issues on GitHub. + +Please note that contributing a small change does in no way mean that you will +be added to the author list of a future paper and/or Zenodo entry! + + +Main Contributors +----------------- + +Almost all contributions to SCINE in general and this repository in specific come +from members of the Reiher research group. + + +Further Contributors +-------------------- + +So far, no one else has contributed to this repository. diff --git a/cmake b/cmake index 3b1d704..668c46e 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 3b1d7046d26c1073eeeaf2bea09ec9fcdd79fe15 +Subproject commit 668c46ef9157631dc158370539b7a6d12d084db3 diff --git a/src/Core/CMakeLists.txt b/src/Core/CMakeLists.txt index dcebb51..d5a01ef 100644 --- a/src/Core/CMakeLists.txt +++ b/src/Core/CMakeLists.txt @@ -24,9 +24,13 @@ target_link_libraries(Core Boost::system ) +if(NOT "${SCINE_MARCH}" STREQUAL "" AND NOT MSVC) + target_compile_options(Core PUBLIC -march=${SCINE_MARCH}) +endif() + + # Add namespaced aliases add_library(Scine::Core ALIAS Core) -add_library(Scine::Core ALIAS Core) # -- Define Install # Headers @@ -48,3 +52,7 @@ scine_install_component_cmake_files( COMPONENT Core EXPORT_NAME CoreTargets ) + +if(SCINE_BUILD_TESTS) + add_subdirectory(Tests) +endif() diff --git a/src/Core/Core/BaseClasses/ObjectWithStructure.h b/src/Core/Core/BaseClasses/ObjectWithStructure.h new file mode 100644 index 0000000..2618c23 --- /dev/null +++ b/src/Core/Core/BaseClasses/ObjectWithStructure.h @@ -0,0 +1,61 @@ +/** + * @file + * @copyright This code is licensed under the 3-clause BSD license.\n + * Copyright ETH Zurich, Laboratory for Physical Chemistry, Reiher Group.\n + * See LICENSE.txt for details. + */ +#ifndef CORE_OBJECTWITHSTRUCTURE_H +#define CORE_OBJECTWITHSTRUCTURE_H + +#include +#include + +namespace Scine { +namespace Utils { +using PositionCollection = Eigen::Matrix; +class AtomCollection; +} // namespace Utils +namespace Core { + +/** + * @class ObjectWithStructure @file ObjectWithStructure.h + * @brief Interface class defining an entity having a molecular structure. + * This solves the diamond inheritance problem where multiple different classes have a setStructure() method. + * A derived class cannot know which method to choose from. + * This way, there is only one setStructure() method. + */ +class ObjectWithStructure { + public: + ObjectWithStructure() = default; + virtual ~ObjectWithStructure() = default; + + /** + * @brief Sets (or changes) the molecular structure. + * + * @param structure A new Utils::AtomCollection to set. + */ + virtual void setStructure(const Utils::AtomCollection& structure) = 0; + /** + * @brief Returns the molecular structure. + * + * @return The molecular structure as const Utils::AtomCollection&. + */ + virtual std::unique_ptr getStructure() const = 0; + /** + * @brief Modifies the atomic coordinates of the molecular structure. + * + * @param newPositions The new atomic coordinates to be assigned to the underlying Utils::AtomCollection. + */ + virtual void modifyPositions(Utils::PositionCollection newPositions) = 0; + /** + * @brief Getter for the atomic coordinates of the molecular structure. + * + * @return The atomic coordinates as const Utils::PositionCollection&. + */ + virtual const Utils::PositionCollection& getPositions() const = 0; +}; + +} // namespace Core +} // namespace Scine + +#endif // CORE_OBJECTWITHSTRUCTURE_H diff --git a/src/Core/Core/BaseClasses/StateHandableObject.h b/src/Core/Core/BaseClasses/StateHandableObject.h new file mode 100644 index 0000000..4b099d9 --- /dev/null +++ b/src/Core/Core/BaseClasses/StateHandableObject.h @@ -0,0 +1,72 @@ +/** + * @file StateHandableObject.h + * @copyright This code is licensed under the 3-clause BSD license.\n + * Copyright ETH Zurich, Laboratory for Physical Chemistry, Reiher Group.\n + * See LICENSE.txt for details. + */ +#ifndef CORE_STATEHANDABLEOBJECT_H_ +#define CORE_STATEHANDABLEOBJECT_H_ +/* External Includes */ +#include + +namespace Scine { +namespace Core { + +/** + * @brief A naming interface for all states to be handled in SCINE + */ +class State { + public: + /// @brief Default constructor. + State() = default; + /// @brief Default destrucor. + virtual ~State() = default; +}; + +/** + * @brief An interface for all objects that should have a handable state. + * + * All objects that have a state or a configuration that should be extractable + * and loadable should inherit from this interface. + * + * The state of such an object is to be encoded into a class derived from State. + * A state should represent a momentary snapshot of a given object + * + * Each such object must then implement the loadState() and getState() + * functions which are hooks for further utilities. These utilities, such as a + * StatesHandler can be found in the Scine::Utils namespace/repository. + */ +class StateHandableObject { + public: + /// @brief Default constructor. + StateHandableObject() = default; + /// @brief Default destrucor. + virtual ~StateHandableObject() = default; + /** + * @brief Loads a given state into the object. + * + * Note that the loaded state may be mutated by the object. It is not + * necessarily copied into the object, even though this is likely the default + * behaviour. Please read the documentation of the specific implementation for + * further details. + * + * @param state The state to be loaded into the object. + */ + virtual void loadState(std::shared_ptr state) = 0; + /** + * @brief Get the current state of the object. + * + * Note that the state is possibly a mutable representation of the current state + * of the object.It is not necessarily a deepcopy, eventhough this is likely the + * default behaviour. Please read the documentation of the specific + * implementation for further details. + * + * @return std::shared_ptr The current state of the object. + */ + virtual std::shared_ptr getState() const = 0; +}; + +} /* namespace Core */ +} /* namespace Scine */ + +#endif /* CORE_STATEHANDABLEOBJECT_H_ */ diff --git a/src/Core/Core/DerivedModule.h b/src/Core/Core/DerivedModule.h index a1e4e2d..e641054 100644 --- a/src/Core/Core/DerivedModule.h +++ b/src/Core/Core/DerivedModule.h @@ -9,14 +9,18 @@ #ifndef CORE_DERIVED_MODULE_ #define CORE_DERIVED_MODULE_ -// Minimal hana headers -#include "boost/hana/for_each.hpp" -#include "boost/hana/keys.hpp" -#include "boost/hana/members.hpp" -#include "boost/hana/none_of.hpp" -#include "boost/hana/size.hpp" +#include "boost/any.hpp" +#include "boost/mpl/at.hpp" +#include "boost/mpl/find_if.hpp" +#include "boost/mpl/for_each.hpp" +#include "boost/mpl/map.hpp" +#include "boost/mpl/size.hpp" +#include "boost/mpl/size_t.hpp" +#include "boost/mpl/vector.hpp" #include -#include +#include +#include +#include #include #include @@ -24,55 +28,371 @@ namespace Scine { namespace Core { namespace DerivedModule { -template -bool has(const std::string& interface, const std::string& model, const Derived& derived) noexcept { - return boost::hana::any_of(boost::hana::accessors(), [&](const auto& accessorPair) { - // Check that the member name matches the interface we are looking for - if (boost::hana::first(accessorPair).c_str() == interface) { - // Look for an interface model in that type's list of supplied interfaces - const auto& models = boost::hana::second(accessorPair)(derived); - auto findIter = std::find(std::begin(models), std::end(models), model); - return findIter != std::end(models); +namespace detail { + +inline bool caseInsensitiveEqual(const std::string& a, const std::string& b) { + return std::equal(std::begin(a), std::end(a), std::begin(b), std::end(b), + [](const char x, const char y) -> bool { return ::tolower(x) == ::tolower(y); }); +} + +// When at end of sequence and Exec has not been called, return the none() +template +struct exec_if_impl { + template + static auto execute(Iter* /* i */, LastIter* /* l */, const Pred& /* p */, const Exec & /* e */) -> + typename Exec::ResultType { + return Exec::none(); + } +}; + +// Non-end iterator of type sequence: Test predicate +template<> +struct exec_if_impl { + template + static auto execute(Iter* /* i */, LastIter* /* l */, const Pred& p, const Exec& e) -> typename Exec::ResultType { + using Item = typename boost::mpl::deref::type; + + if (!p(static_cast(0))) { + using Next = typename boost::mpl::next::type; + return exec_if_impl::value>::execute(static_cast(0), + static_cast(0), p, e); } + else { + return e(static_cast(0)); + } + } +}; - return false; - }); +/*! @brief Run-time "iteration" through type list, if predicate matches, call + * another function, otherwise return its none. + * + * @tparam Sequence a boost::mpl compatible sequence type + * @tparam Predicate A type with a template bool operator() (T* = 0) const + * member specifying whether to execute @p Executable for the type T in @p + * Sequence + * @tparam Executable A type fulfilling the following requirements: + * - Has a ResultType typedef + * - Has a template ResultType operator() (T* = 0) const member which + * is executed if @Predicate returns true for a type in @p Sequence + * - Has a static ResultType none() const member returning the base case + * + * @returns @p Executable::none() if @p Predicate never matches, otherwise @p + * Executable(T) for the first T in @p Sequence that matches Predicate. + */ +template +inline auto exec_if(const Predicate& p, const Executable& e, Sequence* = 0) -> typename Executable::ResultType { + BOOST_MPL_ASSERT((boost::mpl::is_sequence)); + using First = typename boost::mpl::begin::type; + using Last = typename boost::mpl::end::type; + + return exec_if_impl::value>::execute(static_cast(0), static_cast(0), p, e); } -template -std::vector announceInterfaces(const Derived& derived) noexcept { - std::vector interfaces; - interfaces.reserve(boost::hana::size(boost::hana::keys(derived))); +// Predicate type for exec_if +struct MapPairInterfaceIdentifierMatches { + MapPairInterfaceIdentifierMatches(std::string id) : identifier(std::move(id)) { + } + + template + bool operator()(PairType* = 0) const { + using InterfaceType = typename boost::mpl::first::type; + return caseInsensitiveEqual(identifier, InterfaceType::interface); + } + + std::string identifier; +}; + +// Predicate type for exec_if +struct ModelTypeIdentifierMatches { + ModelTypeIdentifierMatches(std::string id) : identifier(std::move(id)) { + } + + template + bool operator()(ModelType* = 0) const { + return caseInsensitiveEqual(identifier, ModelType::model); + } + + std::string identifier; +}; + +// Executable type for exec_if +template +struct CreateInterfacePointer { + using InterfaceTypePtr = std::shared_ptr; + using ResultType = boost::any; + + template + ResultType operator()(ModelType* = 0) const { + return static_cast(std::make_shared()); + } - boost::hana::for_each(boost::hana::keys(derived), [&](const auto& x) { interfaces.emplace_back(x.c_str()); }); + static ResultType none() { + return {}; + } +}; + +// Executable type for exec_if +struct ResolveModel { + ResolveModel(std::string id) : identifier(std::move(id)) { + } + + using ResultType = boost::any; + + template + boost::any operator()(PairType* = 0) const { + using InterfaceType = typename boost::mpl::first::type; + using ModelTypeList = typename boost::mpl::second::type; + + return detail::exec_if(detail::ModelTypeIdentifierMatches{identifier}, + CreateInterfacePointer{}); + } + + static ResultType none() { + return {}; + } + + std::string identifier; +}; + +// Executable type for exec_if +struct MatchFoundExecutor { + using ResultType = bool; + + template + ResultType operator()(T* = 0) const { + return true; + } + + static ResultType none() { + return false; + } +}; + +// Executable type for exec_if +struct ModelExists { + ModelExists(std::string id) : identifier(std::move(id)) { + } + + using ResultType = bool; + + template + ResultType operator()(PairType* = 0) const { + using ModelTypeList = typename boost::mpl::second::type; + + return exec_if(detail::ModelTypeIdentifierMatches{identifier}, MatchFoundExecutor{}); + } + + static ResultType none() { + return false; + } - return interfaces; + std::string identifier; +}; + +// Executable type for exec_if +struct ListModels { + using ResultType = std::vector; + + template + ResultType operator()(PairType* = 0) const { + using ModelTypeList = typename boost::mpl::second::type; + + std::vector models; + models.reserve(boost::mpl::size::value); + // "Iterate" through this entry's list of model types + boost::mpl::for_each>([&](auto t) { + using ModelType = typename decltype(t)::type; + models.push_back(ModelType::model); + }); + + return models; + } + + static ResultType none() { + return {}; + } +}; + +// Compile-time compare C-style strings +constexpr bool strEqual(const char* a, const char* b) { + return *a == *b && (*a == '\0' || strEqual(a + 1, b + 1)); } -template -std::vector announceModels(const std::string& interface, const Derived& derived) noexcept { - std::vector models; +// For a list of model types, test whether any identifiers are duplicate +template +constexpr bool identifiersOverlap() { + constexpr std::size_t S = sizeof...(ModelTypes); - boost::hana::any_of(boost::hana::accessors(), [&](const auto& x) { - if (boost::hana::first(x).c_str() == interface) { - // Copy the models if a match is found - models = boost::hana::second(x)(derived); + const std::array typeIdentifiers{ModelTypes::model...}; - // Early exit - return true; + for (unsigned i = 0; i < S; ++i) { + for (unsigned j = i + 1; j < S; ++j) { + if (strEqual(typeIdentifiers.at(i), typeIdentifiers.at(j))) { + return true; + } } + } - // Keep looking - return false; + return false; +} + +// expansion of all types in the mpl vector to variadic pack for identifiersOverlap +template +constexpr bool identifierOverlapForwarder(std::index_sequence /* inds */) { + return identifiersOverlap>::type...>(); +} + +// MPL Metafunction predicate suitable for find_if, and also for our none_of +struct IdentifierOverlapPredicate { + template + struct apply + : std::integral_constant< + bool, identifierOverlapForwarder::type>( + std::make_index_sequence::type>::type::value>{})> { + }; +}; + +// MPL Metafunction predicate suitable for our none_of +struct ModelTypeListIsEmpty { + template + struct apply + : std::integral_constant::type>::value == 0> {}; +}; + +template +struct none_of + : boost::is_same::type, typename boost::mpl::find_if::type> {}; + +// Value type stating whether all model lists in an mpl map have at least one member type +template +struct ModelListsAreNotEmpty : std::integral_constant::value> {}; + +// Value type stating whether all model lists in an mpl map have no model identifier duplicates +template +struct NoModelIdentifiersOverlap : std::integral_constant::value> {}; + +} // namespace detail + +/** + * @brief Creates an InterfaceType shared_ptr of a matching model to an interface + * + * Helper function to implement a derived module's get function: + * @code{cpp} + * // Transforms this: + * boost::any Derived::get(const std::string& interface, const std::string& model) const { + * if(interface == Calculator::interface) { + * if(model == PlusCalculator::model) { + * return static_cast(std::make_shared()); + * } + * + * if(model == MinusCalculator::model) { + * return static_cast(std::make_shared()); + * } + * + * ... + * } + * + * if(interface == Printer::interface) { + * if(model == SlowPrinter::model) { + * return static_cast(std::make_shared()); + * } + * if(model == EvenSlowerPrinter::model) { + * return static_cast(std::make_shared()); + * } + * + * ... + * } + * + * ... + * + * throw Core::ClassNotImplementedError(); + * } + * + * // Into this: + * boost::any Derived::get(const std::string& interface, const std::string& model) const { + * // NOTE: Same map for has, announceInterfaces, anounceModels + * using Map = boost::mpl::map< + * boost::mpl::pair>, + * boost::mpl::pair> + * >; + * + * boost::any resolved = DerivedModule::resolve(interface, model); + * if(resolved.empty()) { + * throw Core::ClassNotImplementedError {}; + * } + * return resolved; + * } + * @endcode + * + * @tparam MPLMap An mpl::map of InterfaceType -> mpl::vector + * InterfaceType::interface and ModelType::Model must be valid expressions + * @param interface The interface to model + * @param model The model of @p interface to create + * + * @returns a boost any containing a shared_ptr to the InterfaceType of the + * matched model, an empty boost::any otherwise. + */ +template +boost::any resolve(const std::string& interface, const std::string& model) { + static_assert(detail::ModelListsAreNotEmpty::value, "Model type lists may not be empty!"); + + static_assert(detail::NoModelIdentifiersOverlap::value, + "Model identifiers overlap within a single model type list! Model identifiers must be unique within an " + "interface!"); + + return detail::exec_if(detail::MapPairInterfaceIdentifierMatches{interface}, detail::ResolveModel{model}); +} + +/** + * @brief Checks whether a module has a particular model for a particular interface + * + * @tparam MPLMap An mpl::map of InterfaceType -> mpl::vector + * InterfaceType::interface and ModelType::Model must be valid expressions + * @param interface The interface to check for + * @param model The model to check for + * + * @returns whether the module has a model for the interface + */ +template +bool has(const std::string& interface, const std::string& model) noexcept { + return detail::exec_if(detail::MapPairInterfaceIdentifierMatches{interface}, detail::ModelExists{model}); +} + +/** + * @brief Announces all interface names + * + * @tparam MPLMap An mpl::map of InterfaceType -> mpl::vector + * InterfaceType::interface and ModelType::Model must be valid expressions + * + * @return A list of all interface names + */ +template +std::vector announceInterfaces() noexcept { + std::vector interface; + interface.reserve(boost::mpl::size::value); + + boost::mpl::for_each>([&](auto p) { + using PairType = typename decltype(p)::type; + using InterfaceType = typename boost::mpl::first::type; + interface.push_back(InterfaceType::interface); }); - return models; + return interface; } -template -void checkInvariants(const Derived& derived) { - assert(boost::hana::none_of(boost::hana::members(derived), [&derived](const auto& x) { return x.empty(); }) && - "A module may not have empty interface lists!"); +/** + * @brief Announces all model names for a particular interface + * + * @tparam MPLMap An mpl::map of InterfaceType -> mpl::vector + * InterfaceType::interface and ModelType::Model must be valid expressions + * @param interface The interface for which to list model identifiers + * + * @return The list of model names for a particular interface, or an empty list + * if no models exist for that interface. + */ +template +std::vector announceModels(const std::string& interface) noexcept { + return detail::exec_if(detail::MapPairInterfaceIdentifierMatches{interface}, detail::ListModels{}); } } // namespace DerivedModule diff --git a/src/Core/Core/Exceptions.h b/src/Core/Core/Exceptions.h index 8970d84..e67cf74 100644 --- a/src/Core/Core/Exceptions.h +++ b/src/Core/Core/Exceptions.h @@ -56,6 +56,16 @@ class FunctionNotImplementedError : public std::exception { } }; +/** + * @class StateCastingException @file Exception.h + * @brief Exception to be thrown if the state cannot be cast to the desired type. + */ +class StateCastingException : public std::exception { + const char* what() const noexcept final { + return "State pointer does not have a compatible underlying type."; + } +}; + /** * @class InvalidPropertiesException Exceptions.h * @brief Exception thrown when one requires properties from a calculation which cannot be calculated. diff --git a/src/Core/Core/Interfaces/Calculator.h b/src/Core/Core/Interfaces/Calculator.h index ed4ed32..34d96b4 100644 --- a/src/Core/Core/Interfaces/Calculator.h +++ b/src/Core/Core/Interfaces/Calculator.h @@ -7,21 +7,20 @@ #ifndef CORE_CALCULATOR_H_ #define CORE_CALCULATOR_H_ /* Internal Includes */ +#include "Core/BaseClasses/ObjectWithStructure.h" +#include "Core/BaseClasses/StateHandableObject.h" #include "Core/ExportControl.h" /* External Includes */ -#include -#include #include namespace Scine { namespace Utils { -class AtomCollection; class Results; class Settings; class StatesHandler; class PropertyList; -using PositionCollection = Eigen::Matrix; +class AdditiveElectronicContribution; } // namespace Utils namespace Core { @@ -29,41 +28,24 @@ namespace Core { * @class Calculator Calculator.h * @brief The interface for all classes running electronic structure calculations. */ -class Calculator { +class Calculator : public StateHandableObject, public ObjectWithStructure { public: static constexpr const char* interface = "calculator"; - /// @brief Default Constructor. + /// @brief Default constructor. Calculator() = default; - /// @brief Default Destructor. + /// @brief Default destructor. virtual ~Calculator() = default; - /** - * @brief Changes the molecular structure to calculate. - * - * @param structure A new Utils::AtomCollection to save. - */ - virtual void setStructure(const Utils::AtomCollection& structure) = 0; - /** - * @brief Gets the molecular structure as a const Utils::AtomCollection&. - * - * @return a const Utils::AtomCollection&. - */ - virtual std::unique_ptr getStructure() const = 0; - /** - * @brief Allows to modify the positions of the underlying Utils::AtomCollection - * @param newPositions the new positions to be assigned to the underlying Utils::AtomCollection - */ - virtual void modifyPositions(Utils::PositionCollection newPositions) = 0; - /** - * @brief Getter for the coordinates of the underlying Utils::AtomCollection - */ - virtual const Utils::PositionCollection& getPositions() const = 0; /** * @brief Sets the properties to calculate. * @param requiredProperties A Utils::PropertyList, a sequence of bits that represent the * properties that must be calculated. */ virtual void setRequiredProperties(const Utils::PropertyList& requiredProperties) = 0; + /** + * @brief Gets the current properties to calculate. + */ + virtual Utils::PropertyList getRequiredProperties() const = 0; /** * @brief Returns the list of the possible properties to calculate analytically. * By some method analytical hessian calculation is not possible. In this case the @@ -71,7 +53,7 @@ class Calculator { */ virtual Utils::PropertyList possibleProperties() const = 0; /** - * @brief The main function running calculations (dummy). + * @brief The main function running calculations. * * @param description The calculation description. * @return Utils::Result Return the result of the calculation. The object contains the @@ -103,16 +85,6 @@ class Calculator { * @return const Utils::Settings& The settings. */ virtual const Utils::Settings& settings() const = 0; - /** - * @brief Accessor for the StatesHandler. - * @return Utils::StatesHandler& The StatesHandler. - */ - virtual Utils::StatesHandler& statesHandler() = 0; - /** - * @brief Constant accessor for the StatesHandler. - * @return const Utils::StatesHandler& The StatesHandler. - */ - virtual const Utils::StatesHandler& statesHandler() const = 0; /** * @brief Accessor for the saved instance of Utils::Results. * @return Utils::Results& The results of the previous calculation. @@ -123,6 +95,28 @@ class Calculator { * @return const Utils::Results& The results of the previous calculation. */ virtual const Utils::Results& results() const = 0; + /** + * @brief Whether the calculator supports a method family + * + * @param methodFamily identifier for the method family + * + * @return whether the calculator supports a method family + */ + virtual bool supportsMethodFamily(const std::string& methodFamily) const = 0; + /** + * @brief Predicate-generator for ModuleManager's get + * function + * + * @param methodFamily The method family to check support for + * + * @return A lambda capturing the method family that checks passed interface + * pointer objects for method family support + */ + static auto supports(const std::string& methodFamily) { + return [methodFamily](const std::shared_ptr& calculatorPtr) -> bool { + return calculatorPtr->supportsMethodFamily(methodFamily); + }; + } private: /* diff --git a/src/Core/Core/Interfaces/CalculatorWithReference.h b/src/Core/Core/Interfaces/CalculatorWithReference.h new file mode 100644 index 0000000..a8d1d35 --- /dev/null +++ b/src/Core/Core/Interfaces/CalculatorWithReference.h @@ -0,0 +1,106 @@ +/** + * @file Calculator.h + * @copyright This code is licensed under the 3-clause BSD license.\n + * Copyright ETH Zurich, Laboratory for Physical Chemistry, Reiher Group.\n + * See LICENSE.txt for details. + */ +#ifndef CORE_CALCULATORWITHREFERENCE_H +#define CORE_CALCULATORWITHREFERENCE_H +/* Internal Includes */ +#include "Core/ExportControl.h" +/* External Includes */ +#include +#include +#include + +namespace Scine { +namespace Utils { +class Settings; +class Results; +} // namespace Utils +namespace Core { + +class Calculator; + +/** + * @class CalculatorWithReference CalculatorWithReference.h + * @brief The interface for all classes running calculations on top of a + * reference calculation. + * This can be, for example, excited states calculation (CIS, TD-DFT,..), post-HF, + * but also thermodynamics calculations and the CISE approach. + */ +class CalculatorWithReference { + public: + static constexpr const char* interface = "calculatorWithReference"; + /// @brief Default constructor. + CalculatorWithReference() = default; + /// @brief Virtual destructor. + virtual ~CalculatorWithReference() = default; + /** + * @brief Sets the calculator to be used to perform the reference calculation. + * In the derived classes care must be taken that the case where a method does not accept + * some calculator types (i.e CIS with DFT, or TDDFT with HF) is checked and handled. + */ + virtual void setReferenceCalculator(std::shared_ptr referenceCalculator) = 0; + /** + * @brief Performs a reference calculation. + */ + virtual void referenceCalculation() = 0; + + /** + * @brief Accessor for the reference calculator. + * @return Core::Calculator& The reference calculator. + */ + virtual Core::Calculator& getReferenceCalculator() = 0; + /** + * @brief Constant accessor for the reference calculator. + * @return const Core::Calculator& The reference calculator. + */ + virtual const Core::Calculator& getReferenceCalculator() const = 0; + + /** + * @brief Accessor for the calculator, if present. + * @return Core::Calculator& The calculator. + */ + virtual Core::Calculator& getCalculator() = 0; + /** + * @brief Constant accessor for the calculator, if present. + * @return const Core::Calculator& The calculator. + */ + virtual const Core::Calculator& getCalculator() const = 0; + + /** + * @brief The main function running the calculation. + * @return ReturnType Since different methods can give different return types, + * a templetized return type has been chosen. + * The derived class must derive from the right version + * of the CalculatorWithReference class. + */ + virtual Utils::Results calculate() = 0; + + /** + * @brief Getter for the name of the calculator with reference. + * @return Returns the name of the calculator with reference. + */ + virtual std::string name() const = 0; + + /** + * @brief Accessor for the settings. + * @return Utils::Settings& The settings. + */ + virtual Utils::Settings& settings() = 0; + /** + * @brief Constant accessor for the settings. + * @return const Utils::Settings& The settings. + */ + virtual const Utils::Settings& settings() const = 0; + /** + * @brief Method to apply the settings stored in the settings data structure. + */ + virtual void applySettings() = 0; +}; + +} // namespace Core +} // namespace Scine + +#endif // CORE_CALCULATORWITHREFERENCE_H diff --git a/src/Core/Core/Interfaces/MMParametrizer.h b/src/Core/Core/Interfaces/MMParametrizer.h new file mode 100644 index 0000000..d54e41d --- /dev/null +++ b/src/Core/Core/Interfaces/MMParametrizer.h @@ -0,0 +1,63 @@ +/** + * @file MMParametrizer.h + * @copyright This code is licensed under the 3-clause BSD license.\n + * Copyright ETH Zurich, Laboratory for Physical Chemistry, Reiher Group.\n + * See LICENSE.txt for details. + */ + +#ifndef CORE_MMPARAMETRIZER_H +#define CORE_MMPARAMETRIZER_H + +#include + +namespace Scine { + +namespace Utils { +class AtomCollection; +class Settings; +} // namespace Utils + +namespace Core { +/** + * @class MMParametrizer MMParametrizer.h + * @brief The interface for all classes parametrizing a molecular mechanics model. + */ +class MMParametrizer { + public: + static constexpr const char* interface = "mm_parametrizer"; + + /// @brief Default constructor. + MMParametrizer() = default; + /// @brief Default destructor. + virtual ~MMParametrizer() = default; + + /** + * @brief This function generates the MM parameters for a given structure. + * + * Note, that the resulting parameters will be stored in a parameter file that + * is specified in the settings. The same is true for a connectivity file that is generated. + * + * @param structure The molecular system's structure. + */ + virtual void parametrize(Utils::AtomCollection structure) = 0; + /** + * @brief Accessor for the settings. + * @return Utils::Settings& The settings. + */ + virtual Utils::Settings& settings() = 0; + /** + * @brief Constant accessor for the settings. + * @return const Utils::Settings& The settings. + */ + virtual const Utils::Settings& settings() const = 0; + /** + * @brief Getter for the name of the MM parametrizer. + * @return Returns the name of the MM parametrizer. + */ + virtual std::string name() const = 0; +}; + +} // namespace Core +} // namespace Scine + +#endif // CORE_MMPARAMETRIZER_H diff --git a/src/Core/Core/Interfaces/WavefunctionOutputGenerator.h b/src/Core/Core/Interfaces/WavefunctionOutputGenerator.h new file mode 100644 index 0000000..9c4b0b2 --- /dev/null +++ b/src/Core/Core/Interfaces/WavefunctionOutputGenerator.h @@ -0,0 +1,59 @@ +/** + * @file + * @copyright This code is licensed under the 3-clause BSD license.\n + * Copyright ETH Zurich, Laboratory for Physical Chemistry, Reiher Group.\n + * See LICENSE.txt for details. + */ +#ifndef CORE_WAVEFUNCTIONOUTPUTGENERATOR_H +#define CORE_WAVEFUNCTIONOUTPUTGENERATOR_H + +#include + +namespace Scine { +namespace Utils { +class AtomCollection; +} // namespace Utils +namespace Core { + +/** + * @class WavefunctionOutputGenerator @file WavefunctionOutputGenerator.h + * @brief Interface class defining an entity able to generate a wavefunction output file. + */ +class WavefunctionOutputGenerator : public StateHandableObject, public ObjectWithStructure { + public: + static constexpr const char* interface = "wavefunction_output_generator"; + WavefunctionOutputGenerator() = default; + ~WavefunctionOutputGenerator() override = default; + + /** + * @brief Dumps the wavefunction information in a file. + * This function is not const, as it might be necessary to perform an SCF calculation + * beforehand or any other computation that changes members of a derived class. + * @param out The name of the file to which to dump to. + */ + virtual void generateWavefunctionInformation(const std::string& out) = 0; + /** + * @brief Dumps the wavefunction information in a stream. + * This function can be used to implement the previous overload. + * This function is not const, as it might be necessary to perform an SCF calculation + * beforehand or any other computation that changes members of a derived class. + * @param out The stream to which to dump to. + */ + virtual void generateWavefunctionInformation(std::ostream& out) = 0; + + /** + * @brief Accessor for the settings. + * @return Utils::Settings& The settings. + */ + virtual Utils::Settings& settings() = 0; + /** + * @brief Constant accessor for the settings. + * @return const Utils::Settings& The settings. + */ + virtual const Utils::Settings& settings() const = 0; +}; + +} // namespace Core +} // namespace Scine + +#endif // CORE_WAVEFUNCTIONOUTPUTGENERATOR_H diff --git a/src/Core/Core/Module.h b/src/Core/Core/Module.h index 573550a..80acf8f 100644 --- a/src/Core/Core/Module.h +++ b/src/Core/Core/Module.h @@ -43,7 +43,6 @@ namespace Core { * * @code{.cpp} * #include - * #include * #include * #include * @@ -51,29 +50,19 @@ namespace Core { * * class FooModule : public Core::Module { * public: - * // Use hana to define member variables that contain the model string - * // name for interfaces for which you have at least one model - * BOOST_HANA_DEFINE_STRUCT(FooModule, (std::vector, sampleInterfaceName), ...); - * - * // In your module constructor, populate your previously defined vec - * // members with the interfaces' model identifiers that you provide - * FooModule(...) { - * sampleInterfaceName = {"sampleModelName"}; - * } - * - * // Implement name() and get(...) - * // Use DerivedModule.h to implement has(...), announceInterfaces(), + * // Implement name() + * // Use DerivedModule.h to implement has(...), get(...), announceInterfaces(), * // announceModels(...) - * - * // Add a factory - * static std::shared_ptr make() { - * return std::make_shared(...); - * } * }; + * + * std::vector> moduleFactory() { + * return {std::make_shared()}; + * } + * * } // namespace XYZ * * // At global scope, declare an entry point called "moduleFactory" - * BOOST_DLL_ALIAS(XYZ::FooModule::make, moduleFactory) + * BOOST_DLL_ALIAS(XYZ::moduleFactory, moduleFactory) * @endcode */ class Module { @@ -90,6 +79,17 @@ class Module { * @brief Creates a type-erased wrapper around a shared_ptr to a model of a * interface. * + * @note Derived classes can implement this function using DerivedModule.h: + * @code{.cpp} + * boost::any FooModule::get(const std::string& interface, const std::string& model) const final { + * boost::any resolved = Scine::Core::DerivedModule::resolve(interface, model); + * + * if (resolved.empty()) { + * throw Scine::Core::ClassNotImplementedError(); + * } + * return resolved; + * } + * @endcode * @throws X If the derived class does not supply models of the interface. * * @returns A type-erased interface model. Use try_cast to extract a pointer @@ -101,11 +101,10 @@ class Module { /*! * @brief Checks if this module supplies a particular model of an interface. * - * @note Derived classes that make use of BOOST_HANA_DEFINE_STRUCT can - * implement this function using DerivedModule.h: + * @note Derived classes can implement this function using DerivedModule.h: * @code{.cpp} * bool FooModule::has(const std::string& interface, const std::string& model) const noexcept final { - * return Core::DerivedModule::has(interface, model, *this); + * return Core::DerivedModule::has(interface, model); * } * @endcode */ @@ -115,11 +114,10 @@ class Module { /*! * @brief Announces all interfaces of which the module provides at least one model * - * @note Derived classes that make use of BOOST_HANA_DEFINE_STRUCT can - * implement this function using DerivedModule.h: + * @note Derived classes can implement this function using DerivedModule.h: * @code{.cpp} * bool FooModule::announceInterfaces() const noexcept final { - * return Core::DerivedModule::announceInterfaces(*this); + * return Core::DerivedModule::announceInterfaces(); * } * @endcode */ @@ -130,11 +128,10 @@ class Module { * @note If the class supplies no models of a particular interface, this list * is empty. * - * @note Derived classes that make use of BOOST_HANA_DEFINE_STRUCT can - * implement this function using DerivedModule.h: + * @note Derived classes can implement this function using DerivedModule.h: * @code{.cpp} * bool FooModule::announceModels(const std::string& interface) const noexcept final { - * return Core::DerivedModule::announceModels(interface, *this); + * return Core::DerivedModule::announceModels(interface); * } * @endcode */ diff --git a/src/Core/Core/ModuleManager.cpp b/src/Core/Core/ModuleManager.cpp index ce0e5ba..00afa94 100644 --- a/src/Core/Core/ModuleManager.cpp +++ b/src/Core/Core/ModuleManager.cpp @@ -23,9 +23,11 @@ bool patternMatchModuleLibrary(const std::string& filename) { } // namespace detail -struct ModuleManager::LibraryAndModule { +struct ModuleManager::LibraryAndModules { + using ModulePtr = std::shared_ptr; + using ModuleList = std::vector; boost::dll::shared_library library; - std::shared_ptr modulePtr; + ModuleList modules; static boost::dll::shared_library tryLoad(const boost::filesystem::path& libraryPath) { boost::system::error_code error; @@ -45,31 +47,33 @@ struct ModuleManager::LibraryAndModule { * not default-constructible */ - explicit LibraryAndModule(boost::dll::shared_library shlib) : library(std::move(shlib)) { + explicit LibraryAndModules(boost::dll::shared_library shlib) : library(std::move(shlib)) { if (!library.has("moduleFactory")) { throw std::runtime_error("Module loaded does not have signature for Core"); } // Predefine the function signature of the module factory function - using ModuleFactorySignature = std::shared_ptr(); + using ModuleFactorySignature = std::vector>(); /* Import the module factory function symbol from the shared library and * immediately call it, constructing a module base class pointer */ - modulePtr = library.get_alias("moduleFactory")(); + modules = library.get_alias("moduleFactory")(); } - explicit LibraryAndModule(const boost::filesystem::path& libraryPath) : LibraryAndModule(tryLoad(libraryPath)) { + explicit LibraryAndModules(const boost::filesystem::path& libraryPath) : LibraryAndModules(tryLoad(libraryPath)) { } }; -std::vector ModuleManager::_sources; +std::vector ModuleManager::_sources; void ModuleManager::load(const boost::filesystem::path& libraryPath) { // Forward the path to the LibraryAndModule constructor - LibraryAndModule lib(libraryPath); + LibraryAndModules lib(libraryPath); // Throw if the module is already loaded - if (moduleLoaded(lib.modulePtr->name())) { - throw std::runtime_error("Module is already loaded!"); + for (auto& modulePtr : lib.modules) { + if (moduleLoaded(modulePtr->name())) { + throw std::runtime_error("Module is already loaded"); + } } _sources.push_back(std::move(lib)); @@ -77,11 +81,13 @@ void ModuleManager::load(const boost::filesystem::path& libraryPath) { void ModuleManager::load(boost::dll::shared_library library) { // Forward the shared_library to the LibraryAndModule constructor - LibraryAndModule lib(std::move(library)); + LibraryAndModules lib(std::move(library)); // Throw if the module is already loaded - if (moduleLoaded(lib.modulePtr->name())) { - throw std::runtime_error("Module is already loaded!"); + for (auto& modulePtr : lib.modules) { + if (moduleLoaded(modulePtr->name())) { + throw std::runtime_error("Module is already loaded"); + } } _sources.push_back(std::move(lib)); @@ -91,7 +97,9 @@ std::vector ModuleManager::getLoadedModuleNames() const { std::vector list; list.reserve(_sources.size()); for (const auto& source : _sources) { - list.push_back(source.modulePtr->name()); + for (const auto& modulePtr : source.modules) { + list.push_back(modulePtr->name()); + } } return list; } @@ -101,12 +109,14 @@ std::vector ModuleManager::getLoadedInterfaces() const { std::vector interfaces; for (const auto& source : _sources) { - auto sourceInterfaces = source.modulePtr->announceInterfaces(); - for (auto& interfaceName : sourceInterfaces) { - auto findIter = std::lower_bound(std::begin(interfaces), std::end(interfaces), interfaceName); + for (const auto& modulePtr : source.modules) { + auto sourceInterfaces = modulePtr->announceInterfaces(); + for (auto& interfaceName : sourceInterfaces) { + auto findIter = std::lower_bound(std::begin(interfaces), std::end(interfaces), interfaceName); - if (findIter == std::end(interfaces) || *findIter != interfaceName) { - interfaces.insert(findIter, std::move(interfaceName)); + if (findIter == std::end(interfaces) || *findIter != interfaceName) { + interfaces.insert(findIter, std::move(interfaceName)); + } } } } @@ -118,35 +128,48 @@ std::vector ModuleManager::getLoadedModels(const std::string& inter std::vector models; for (const auto& source : _sources) { - auto sourceModels = source.modulePtr->announceModels(interface); - std::move(std::begin(sourceModels), std::end(sourceModels), std::back_inserter(models)); + for (const auto& modulePtr : source.modules) { + auto sourceModels = modulePtr->announceModels(interface); + std::move(std::begin(sourceModels), std::end(sourceModels), std::back_inserter(models)); + } } return models; } -bool ModuleManager::has(const std::string& interface, const std::string& model, const std::string moduleName) const { +bool ModuleManager::has(const std::string& interface, const std::string& model, const std::string& moduleName) const { + // If the module name is specified, look in detailed fashion if (!moduleName.empty()) { for (const auto& source : _sources) { - if (source.modulePtr->has(interface, model)) { - return (source.modulePtr->name() == moduleName) || (source.modulePtr->name() == moduleName + "Module"); + for (const auto& modulePtr : source.modules) { + if (modulePtr->name() != moduleName && modulePtr->name() != moduleName + "Module") { + continue; + } + + if (modulePtr->has(interface, model)) { + return true; + } } } return false; } - else { - for (const auto& source : _sources) { - if (source.modulePtr->has(interface, model)) { + + for (const auto& source : _sources) { + for (const auto& modulePtr : source.modules) { + if (modulePtr->has(interface, model)) { return true; } } - return false; } + + return false; } bool ModuleManager::moduleLoaded(const std::string& moduleName) const { - return std::find_if(std::begin(_sources), std::end(_sources), [&moduleName](const LibraryAndModule& libModule) -> bool { - return moduleName == libModule.modulePtr->name(); + return std::find_if(std::begin(_sources), std::end(_sources), [&moduleName](const LibraryAndModules& libModule) -> bool { + return std::find_if(std::begin(libModule.modules), std::end(libModule.modules), + [&moduleName](const auto& modulePtr) -> bool { return moduleName == modulePtr->name(); }) != + std::end(libModule.modules); }) != std::end(_sources); } @@ -161,7 +184,7 @@ ModuleManager::ModuleManager() { std::for_each(boost::filesystem::directory_iterator(directoryPath), end, [&](const auto& filePath) { if (detail::patternMatchModuleLibrary(filePath.path().filename().string())) { try { - load(filePath); + this->load(filePath); } catch (...) { } @@ -215,25 +238,62 @@ ModuleManager::ModuleManager() { } } -boost::any ModuleManager::_get(const std::string& interface, const std::string& model, const std::string moduleName) const { +boost::any ModuleManager::_get(const std::string& interface, const std::string& model, const std::string& moduleName) const { if (!moduleName.empty()) { for (const auto& source : _sources) { - if (source.modulePtr->has(interface, model)) { - if ((source.modulePtr->name() == moduleName) || (source.modulePtr->name() == moduleName + "Module")) { - return source.modulePtr->get(interface, model); + for (const auto& modulePtr : source.modules) { + if ((modulePtr->name() != moduleName) && (modulePtr->name() != moduleName + "Module")) { + continue; + } + + if (modulePtr->has(interface, model)) { + return modulePtr->get(interface, model); } } } } - else { - for (const auto& source : _sources) { - if (source.modulePtr->has(interface, model)) { - return source.modulePtr->get(interface, model); + + for (const auto& source : _sources) { + for (const auto& modulePtr : source.modules) { + if (modulePtr->has(interface, model)) { + return modulePtr->get(interface, model); } } } + throw ClassNotImplementedError{}; } +std::vector ModuleManager::_getAll(const std::string& interface, const std::string& moduleName) const { + std::vector models; + + if (!moduleName.empty()) { + for (const auto& source : _sources) { + for (const auto& modulePtr : source.modules) { + if ((modulePtr->name() != moduleName) && (modulePtr->name() != moduleName + "Module")) { + continue; + } + + for (const auto& model : modulePtr->announceModels(interface)) { + models.push_back(modulePtr->get(interface, model)); + } + + return models; + } + } + return models; + } + + for (const auto& source : _sources) { + for (const auto& modulePtr : source.modules) { + for (const auto& model : modulePtr->announceModels(interface)) { + models.push_back(modulePtr->get(interface, model)); + } + } + } + + return models; +} + } /* namespace Core */ } /* namespace Scine */ diff --git a/src/Core/Core/ModuleManager.h b/src/Core/Core/ModuleManager.h index 6089cf9..35401ee 100644 --- a/src/Core/Core/ModuleManager.h +++ b/src/Core/Core/ModuleManager.h @@ -46,7 +46,7 @@ class CORE_EXPORT ModuleManager { /** * @brief Static instance getter. * - * This static function assures the single instanciation of a ModuleManager + * This static function assures the single instantiation of a ModuleManager * for more information google the singleton design pattern. * * @return Returns a reference to the ModuleManager instance. @@ -147,7 +147,7 @@ class CORE_EXPORT ModuleManager { * @return Whether the model is available. If so, get() should be safe to call. */ template - bool has(const std::string& model, const std::string moduleName = "") const { + bool has(const std::string& model, const std::string& moduleName = "") const { return has(Interface::interface, model, moduleName); } @@ -163,7 +163,7 @@ class CORE_EXPORT ModuleManager { * * @return Whether the model is available. If so, get() should be safe to call. */ - bool has(const std::string& interface, const std::string& model, const std::string moduleName = "") const; + bool has(const std::string& interface, const std::string& model, const std::string& moduleName = "") const; /** * @brief Creates a model of an interface and returns a base-class pointer. @@ -185,12 +185,52 @@ class CORE_EXPORT ModuleManager { * interface. */ template - std::shared_ptr get(const std::string& model, const std::string moduleName = "") const { + std::shared_ptr get(const std::string& model, const std::string& moduleName = "") const { auto any = _get(Interface::interface, model, moduleName); assert(!any.empty() && "Contract of a module's get states that the any may not be empty!"); return boost::any_cast>(any); } + /** + * @brief Find a model satisfying a predicate + * + * @tparam Interface The interface class type (e.g. Calculator) + * @tparam UnaryPredicate Unary predicate function or functor of signature (std::shared_ptr -> bool) + * @param predicate instance of UnaryPredicate + * @param moduleName String identifier of a module. If given, searches for + * models of @p Interface only in that module. + * + * @throws std::runtime_error If no models of this interface are loaded or if + * no model matches the supplied predicate. + * + * @return A non-empty interface shared pointer + */ + template + std::enable_if_t::value, std::shared_ptr> + get(UnaryPredicate&& predicate, const std::string& moduleName = "") const { + /* NOTE: Yes, this implementation is a little wasteful in that it gathers + * all models, and only then tests the predicate, but this is necessary + * to maintain type erasure. Only here do we know what interface type the + * models actually are and only here can we call a predicate on them. + */ + auto anys = _getAll(Interface::interface, moduleName); + + if (anys.empty()) { + throw std::runtime_error("There are no models of this interface loaded."); + } + + for (auto& any : anys) { + assert(!any.empty() && "Contract of a module's get states the any may not be empty!"); + auto interfacePtr = boost::any_cast>(any); + assert(interfacePtr && "Contract of module's get states the wrapped pointer may not be empty!"); + if (predicate(interfacePtr)) { + return interfacePtr; + } + } + + throw std::runtime_error("No model matches the supplied predicate!"); + } + /** * @brief Checks whether a particular module is loaded * @@ -220,14 +260,28 @@ class CORE_EXPORT ModuleManager { * * @return A boost::any-wrapped std::shared_ptr */ - boost::any _get(const std::string& interface, const std::string& model, const std::string moduleName = "") const; + boost::any _get(const std::string& interface, const std::string& model, const std::string& moduleName = "") const; + + /** + * @brief Fetches all models of an interface, optionally just from a single module + * + * @param interface interface identifier that the module matches + * @param moduleName The string identifier of a particular model. If not + * empty, queries for models only in that particular + * module. Module names will also be expanded: 'XYZ' will + * also query for 'XYZModule' + * + * @return a vector of boost::anys wrapping std::shared_ptr. The + * vector may be empty, the shared_ptrs will not. + */ + std::vector _getAll(const std::string& interface, const std::string& moduleName = "") const; /** * @brief Named union type to associate a shared library with its module */ - struct LibraryAndModule; + struct LibraryAndModules; - static std::vector _sources; + static std::vector _sources; }; } /* namespace Core */ diff --git a/src/Core/Files.cmake b/src/Core/Files.cmake index 856e6cb..98a955c 100644 --- a/src/Core/Files.cmake +++ b/src/Core/Files.cmake @@ -1,9 +1,13 @@ set(CORE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/Core/Interfaces/Calculator.h + ${CMAKE_CURRENT_SOURCE_DIR}/Core/Interfaces/CalculatorWithReference.h + ${CMAKE_CURRENT_SOURCE_DIR}/Core/Interfaces/MMParametrizer.h ${CMAKE_CURRENT_SOURCE_DIR}/Core/DerivedModule.h ${CMAKE_CURRENT_SOURCE_DIR}/Core/Exceptions.h ${CMAKE_CURRENT_SOURCE_DIR}/Core/ExportControl.h ${CMAKE_CURRENT_SOURCE_DIR}/Core/Module.h ${CMAKE_CURRENT_SOURCE_DIR}/Core/ModuleManager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Core/ModuleManager.h + ${CMAKE_CURRENT_SOURCE_DIR}/Core/BaseClasses/StateHandableObject.h + ${CMAKE_CURRENT_SOURCE_DIR}/Core/BaseClasses/ObjectWithStructure.h ) diff --git a/src/Core/Tests/CMakeLists.txt b/src/Core/Tests/CMakeLists.txt new file mode 100644 index 0000000..85334cc --- /dev/null +++ b/src/Core/Tests/CMakeLists.txt @@ -0,0 +1,15 @@ +# Build a shared library module from SampleModule and the models +add_library(SampleModule SHARED + ${CMAKE_CURRENT_SOURCE_DIR}/SampleModule.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/DummyModels.cpp +) +set_target_properties(SampleModule PROPERTIES + POSITION_INDEPENDENT_CODE ON + OUTPUT_NAME samplemodule +) +target_link_libraries(SampleModule PUBLIC Scine::Core) + +# Build the tests +add_executable(CoreTests CoreTests.cpp) +target_link_libraries(CoreTests PRIVATE Scine::Core gtest_main gmock) +add_test(NAME Core COMMAND CoreTests) diff --git a/src/Core/Tests/CoreTests.cpp b/src/Core/Tests/CoreTests.cpp new file mode 100644 index 0000000..a72c449 --- /dev/null +++ b/src/Core/Tests/CoreTests.cpp @@ -0,0 +1,45 @@ +/** + * @file CoreTests.cpp + * @copyright This code is licensed under the 3-clause BSD license.\n + * Copyright ETH Zurich, Laboratory for Physical Chemistry, Reiher Group.\n + * See LICENSE.txt for details. + */ +#include "DummyInterface.h" +#include +#include + +using namespace Scine; +using namespace Core; + +TEST(CoreModules, ModuleCorrectness) { + auto& manager = ModuleManager::getInstance(); + + if (!manager.moduleLoaded("SampleModule")) { + manager.load("samplemodule"); + } + + const auto moduleNames = manager.getLoadedModuleNames(); + ASSERT_FALSE(moduleNames.empty()); + ASSERT_EQ(moduleNames.front(), "SampleModule"); + ASSERT_TRUE(manager.moduleLoaded("SampleModule")); + + const auto interfaces = manager.getLoadedInterfaces(); + ASSERT_FALSE(interfaces.empty()); + ASSERT_TRUE(std::find(interfaces.begin(), interfaces.end(), "dummy_interface") != interfaces.end()); + + const auto models = manager.getLoadedModels("dummy_interface"); + ASSERT_EQ(models.size(), 2); + ASSERT_EQ(models.front(), "dummy_a"); + ASSERT_EQ(models.back(), "dummy_b"); + + ASSERT_TRUE(manager.has("dummy_interface", "dummy_a")); + ASSERT_TRUE(manager.has("dummy_interface", "dummy_b")); + ASSERT_TRUE(manager.has("dummy_interface", "dummy_a", "SampleModule")); + ASSERT_FALSE(manager.has("dummy_interface", "nonexistent_model")); + + auto barPtr = manager.get("dummy_a"); + ASSERT_EQ(barPtr->name(), "DummyA"); + + auto bazPtr = manager.get("dummy_b"); + ASSERT_EQ(bazPtr->name(), "DummyB"); +} diff --git a/src/Core/Tests/DummyInterface.h b/src/Core/Tests/DummyInterface.h new file mode 100644 index 0000000..8b7a4c5 --- /dev/null +++ b/src/Core/Tests/DummyInterface.h @@ -0,0 +1,32 @@ +/** + * @file DummyInterfaceAndModels.h + * @copyright This code is licensed under the 3-clause BSD license.\n + * Copyright ETH Zurich, Laboratory for Physical Chemistry, Reiher Group.\n + * See LICENSE.txt for details. + */ +#ifndef DUMMY_INTERFACE_H +#define DUMMY_INTERFACE_H +/* External Includes */ +#include +#include + +namespace Scine { +namespace Core { +/** + * @class DummyInterface + * @brief A dummy interface class + */ +class DummyInterface { + public: + static constexpr const char* interface = "dummy_interface"; + + DummyInterface() = default; + virtual ~DummyInterface() = default; + + virtual std::string name() const = 0; +}; + +} /* namespace Core */ +} /* namespace Scine */ + +#endif /* DUMMY_INTERFACE_H */ diff --git a/src/Core/Tests/DummyModels.cpp b/src/Core/Tests/DummyModels.cpp new file mode 100644 index 0000000..7df9236 --- /dev/null +++ b/src/Core/Tests/DummyModels.cpp @@ -0,0 +1,19 @@ +/** + * @file DummyModels.cpp + * @copyright This code is licensed under the 3-clause BSD license.\n + * Copyright ETH Zurich, Laboratory for Physical Chemistry, Reiher Group.\n + * See LICENSE.txt for details. + */ +#include "DummyModels.h" + +namespace sample_namespace { + +std::string DummyModelA::name() const { + return "DummyA"; +} + +std::string DummyModelB::name() const { + return "DummyB"; +} + +} // namespace sample_namespace diff --git a/src/Core/Tests/DummyModels.h b/src/Core/Tests/DummyModels.h new file mode 100644 index 0000000..0d0d005 --- /dev/null +++ b/src/Core/Tests/DummyModels.h @@ -0,0 +1,27 @@ +/** + * @file DummyInterfaceAndModels.h + * @copyright This code is licensed under the 3-clause BSD license.\n + * Copyright ETH Zurich, Laboratory for Physical Chemistry, Reiher Group.\n + * See LICENSE.txt for details. + */ +#ifndef DUMMY_MODELS_H +#define DUMMY_MODELS_H +/* External Includes */ +#include "DummyInterface.h" +#include + +namespace sample_namespace { + +struct DummyModelA : public Scine::Core::DummyInterface { + static constexpr const char* model = "dummy_a"; + std::string name() const final; +}; + +struct DummyModelB : public Scine::Core::DummyInterface { + static constexpr const char* model = "dummy_b"; + std::string name() const final; +}; + +} // namespace sample_namespace + +#endif /* DUMMY_INTERFACE_H */ diff --git a/src/Core/Tests/SampleModule.cpp b/src/Core/Tests/SampleModule.cpp new file mode 100644 index 0000000..e5542c4 --- /dev/null +++ b/src/Core/Tests/SampleModule.cpp @@ -0,0 +1,96 @@ +/** + * @file SampleModule.cpp + * @copyright This code is licensed under the 3-clause BSD license.\n + * Copyright ETH Zurich, Laboratory for Physical Chemistry, Reiher Group.\n + * See LICENSE.txt for details. + * + * Steps to your own module implementation: + * + * - Adjust the first include according to the rename + * - Remove the DummyInterface and DummyModels includes + * - Add includes to all of your classes implementing a Core interface that you + * wish to provide + * - Adjust the sample_namespace namespace + * - Adjust the InterfaceModelMap using declaration + * - Adjust all instances of the SampleModule class name + * - Adjust the string in ::name to provide a string identifying your Module + * - Delete this documentation + */ +#include "SampleModule.h" +#include "DummyInterface.h" +#include "DummyModels.h" +#include +#include + +namespace sample_namespace { + +/* Instructions and examples to adjust the interface to model map typedef + * + * Welcome to template metaprogramming! This map contains only type names + * instead of values. Apart from that little difference, conceptually it is + * still a map and denotes which interface models belong to which interfaces. + * To adjust this to the interface models your module will provide: + * + * - For each interface for which you have a class implementing it, add a pair + * as below to the map, separated by commas. + * - If you have multiple classes implementing a particular interface, add them + * within the vector part of the pair. + * + * E.g. If, besides the `DummyInterface` and its models `DummyModelA` and + * `DummyModelB`, there were an additional interface called `SampleInterface` and + * you had a class implementing its interface `SampleModel`, you would + * add the lines marked with a `+`: + * + * using InterfaceModelMap = boost::mpl::map< + * boost::mpl::pair< + * Scine::Core::DummyInterface, + * boost::mpl::vector // Comma between model names + * + >, // Comma needed here + * + boost::mpl::pair< + * + Scine::Core::SampleInterface, + * + boost::mpl::vector + * + > + * >; + * + * After you are done, delete this documentation or leave it or write your own. + * You can always refer to this source file again if you need to. + */ +using InterfaceModelMap = + boost::mpl::map>>; + +std::string SampleModule::name() const noexcept { + return "SampleModule"; +} + +boost::any SampleModule::get(const std::string& interface, const std::string& model) const { + boost::any resolved = Scine::Core::DerivedModule::resolve(interface, model); + + // Throw an exception if we could not match an interface or model + if (resolved.empty()) { + throw Scine::Core::ClassNotImplementedError(); + } + + return resolved; +} + +bool SampleModule::has(const std::string& interface, const std::string& model) const noexcept { + return Scine::Core::DerivedModule::has(interface, model); +} + +std::vector SampleModule::announceInterfaces() const noexcept { + return Scine::Core::DerivedModule::announceInterfaces(); +} + +std::vector SampleModule::announceModels(const std::string& interface) const noexcept { + return Scine::Core::DerivedModule::announceModels(interface); +} + +std::shared_ptr SampleModule::make() { + return std::make_shared(); +} + +std::vector> moduleFactory() { + return {SampleModule::make()}; +} + +} /* namespace sample_namespace */ diff --git a/src/Core/Tests/SampleModule.h b/src/Core/Tests/SampleModule.h new file mode 100644 index 0000000..10b3f0b --- /dev/null +++ b/src/Core/Tests/SampleModule.h @@ -0,0 +1,71 @@ +/** + * @file SampleModule.h + * @copyright This code is licensed under the 3-clause BSD license.\n + * Copyright ETH Zurich, Laboratory for Physical Chemistry, Reiher Group.\n + * See LICENSE.txt for details. + * + * + * Steps to make this your own module header: + * + * - Replace the preprocessor variable after #ifndef, #define and #endif + * - Adjust all instances of the placeholder namespace name (sample_namespace) + * - Adjust all instances of the SampleModule class name and rename this file + * and its matching .cpp accordingly + * - Delete this documentation + */ +#ifndef SAMPLE_MODULE_H +#define SAMPLE_MODULE_H + +#include +#include +#include + +namespace sample_namespace { + +/** + * @brief Your module class definition. + */ +class SampleModule : public Scine::Core::Module { + public: + std::string name() const noexcept final; + + boost::any get(const std::string& interface, const std::string& model) const final; + + bool has(const std::string& interface, const std::string& model) const noexcept final; + + std::vector announceInterfaces() const noexcept final; + + std::vector announceModels(const std::string& interface) const noexcept final; + + static std::shared_ptr make(); +}; + +// Shared library entry point creating pointers to all contained modules +std::vector> moduleFactory(); + +} // namespace sample_namespace + +#ifdef __MINGW32__ +/* MinGW builds are problematic. We build with default visibility, and adding + * an attribute __dllexport__ specifically for this singular symbol leads to the + * loss of all other weak symbols. Essentially, here we have just expanded the + * BOOST_DLL_ALIAS macro in order to declare the type-erased const void* + * 'moduleFactory' without any symbol visibility attribute additions that could + * confuse the MinGW linker, which per Boost DLL documentation is unable to mix + * weak attributes and __dllexport__ correctly. + * + * If ever the default visibility for this translation unit is changed, we + * will have to revisit this bit of code for the MinGW platform again. + * + * Additionally, more recent Boost releases may have fixed this problem. + * See the macro BOOST_DLL_FORCE_ALIAS_INSTANTIATIONS as used in the library's + * example files. + */ +extern "C" { +const void* moduleFactory = reinterpret_cast(reinterpret_cast(&sample_namespace::moduleFactory)); +} +#else +BOOST_DLL_ALIAS(sample_namespace::moduleFactory, moduleFactory); +#endif + +#endif /* SAMPLE_MODULE */ diff --git a/src/Core/config.cmake.in b/src/Core/config.cmake.in index 107eede..4aafada 100644 --- a/src/Core/config.cmake.in +++ b/src/Core/config.cmake.in @@ -1,13 +1,17 @@ # Add dependencies for your component -set(Boost_USE_STATIC_LIBS ON) include(CMakeFindDependencyMacro) if(NOT TARGET Boost::filesystem OR NOT TARGET Boost::system) + set(Boost_USE_STATIC_LIBS OFF) unset(Boost_FOUND) find_dependency(Boost REQUIRED COMPONENTS filesystem system) endif() - -set(Core_VERSION 0.1.0) +if(DEFINED SCINE_MARCH AND NOT "${SCINE_MARCH}" STREQUAL "@SCINE_MARCH@") + message(FATAL_ERROR "You are trying to use an installed version of SCINE Core that was compiled with different -march flag values! Current march flags: ${SCINE_MARCH}, installed flags: @SCINE_MARCH@") +endif() +if (NOT DEFINED SCINE_MARCH) + set(SCINE_MARCH "@SCINE_MARCH@") +endif() include(${CMAKE_CURRENT_LIST_DIR}/CoreTargets.cmake)