From fcddf16f79cb125a73cf863ab26c011160990cab Mon Sep 17 00:00:00 2001 From: tmadlener Date: Mon, 29 Jul 2024 18:51:14 +0200 Subject: [PATCH] Rename Association to Link after EDM4hep discussion --- doc/{associations.md => links.md} | 102 ++--- include/podio/AssociationCollection.h | 37 -- include/podio/LinkCollection.h | 35 ++ include/podio/detail/Association.h | 289 ------------ .../detail/AssociationCollectionIterator.h | 47 -- include/podio/detail/AssociationFwd.h | 101 ----- include/podio/detail/Link.h | 289 ++++++++++++ ...nCollectionData.h => LinkCollectionData.h} | 38 +- ...nCollectionImpl.h => LinkCollectionImpl.h} | 76 ++-- include/podio/detail/LinkCollectionIterator.h | 47 ++ include/podio/detail/LinkFwd.h | 100 +++++ .../detail/{AssociationObj.h => LinkObj.h} | 24 +- .../{AssociationSIOBlock.h => LinkSIOBlock.h} | 26 +- python/podio/test_Frame.py | 2 +- src/CMakeLists.txt | 2 +- src/DatamodelRegistry.cc | 2 +- tests/CMakeLists.txt | 4 +- tests/DatamodelAssociations.cc | 6 - tests/DatamodelLinks.cc | 6 + tests/frame_test_common.h | 2 +- tests/read_test.h | 30 +- tests/unittests/CMakeLists.txt | 2 +- tests/unittests/associations.cpp | 412 ------------------ tests/unittests/links.cpp | 412 ++++++++++++++++++ tests/write_frame.h | 26 +- 25 files changed, 1057 insertions(+), 1060 deletions(-) rename doc/{associations.md => links.md} (65%) delete mode 100644 include/podio/AssociationCollection.h create mode 100644 include/podio/LinkCollection.h delete mode 100644 include/podio/detail/AssociationCollectionIterator.h delete mode 100644 include/podio/detail/AssociationFwd.h create mode 100644 include/podio/detail/Link.h rename include/podio/detail/{AssociationCollectionData.h => LinkCollectionData.h} (82%) rename include/podio/detail/{AssociationCollectionImpl.h => LinkCollectionImpl.h} (79%) create mode 100644 include/podio/detail/LinkCollectionIterator.h create mode 100644 include/podio/detail/LinkFwd.h rename include/podio/detail/{AssociationObj.h => LinkObj.h} (50%) rename include/podio/detail/{AssociationSIOBlock.h => LinkSIOBlock.h} (71%) delete mode 100644 tests/DatamodelAssociations.cc create mode 100644 tests/DatamodelLinks.cc delete mode 100644 tests/unittests/associations.cpp create mode 100644 tests/unittests/links.cpp diff --git a/doc/associations.md b/doc/links.md similarity index 65% rename from doc/associations.md rename to doc/links.md index b767aa3bd..e26328e92 100644 --- a/doc/associations.md +++ b/doc/links.md @@ -1,65 +1,65 @@ # Associating unrelated objects with each other -Sometimes it is necessary to build associations between objects whose datatypes are +Sometimes it is necessary to build links between objects whose datatypes are not related via a `OneToOneRelation` or a `OneToManyRelation`. These *external -relations* are called *Associations* in podio, and they are implemented as a +relations* are called *Links* in podio, and they are implemented as a templated version of the code that would be generated by the following yaml snippet (in this case between generic `FromT` and `ToT` datatypes): ```yaml -Association: - Description: "A weighted association between a FromT and a ToT" +Link: + Description: "A weighted link between a FromT and a ToT" Author: "P. O. Dio" Members: - - float weight // the weight of the association + - float weight // the weight of the link OneToOneRelations: - FromT from // reference to the FromT - ToT to // reference to the ToT ``` -## `Association` basics -`Association`s are implemented as templated classes forming a similar structure +## `Link` basics +`Link`s are implemented as templated classes forming a similar structure as other podio generated classes, with several layers of which users only ever interact with the *User layer*. This layer has the following basic classes ```cpp /// The collection class that forms the basis of I/O and also is the main entry point template -class AssociationCollection; +class LinkCollection; /// The default (immutable) class that one gets after reading a collection template -class Association; +class Link; -/// The mutable class for creating associations before writing them +/// The mutable class for creating links before writing them template -class MutableAssociation; +class MutableLink; ``` Although the names of the template parameters, `FromT` and `ToT` imply a -direction of the association, from a technical point of view nothing actually +direction of the link, from a technical point of view nothing actually enforces this direction, unless `FromT` and `ToT` are both of the same type. -Hence, associations can effectively be treated as bi-directional, and one +Hence, links can effectively be treated as bi-directional, and one combination of `FromT` and `ToT` should be enough for all use cases (see also -the [usage section](#how-to-use-associations)). +the [usage section](#how-to-use-links)). For a more detailed explanation of the internals and the actual implementation see [the implementation details](#implementation-details). -## How to use `Association`s -Using `Association`s is quite simple. In line with other datatypes that are +## How to use `Link`s +Using `Link`s is quite simple. In line with other datatypes that are generated by podio all the functionality can be gained by including the corresponding `Collection` header. After that it is generally recommended to -introduce a type alias for easier usage. **As a general rule `Associations` need +introduce a type alias for easier usage. **As a general rule `Links` need to be declared with the default (immutable) types.** Trying to instantiate them with `Mutable` types will result in a compilation error. ```cpp -#include "podio/AssociationCollection.h" +#include "podio/LinkCollection.h" #include "edm4hep/MCParticleCollection.h" #include "edm4hep/ReconstructedParticleCollection.h" -// declare a new association type -using MCRecoParticleAssociationCollection = podio::AssociationCollection; ``` @@ -68,8 +68,8 @@ This can now be used exactly as any other podio generated collection, i.e. edm4hep::MCParticle mcParticle{}; edm4hep::ReconstructedParticle recoParticle{}; -auto mcRecoAssocs = MCRecoParticleAssociationCollection{}; -auto assoc = mcRecoAssocs.create(); // create an association; +auto mcRecoAssocs = MCRecoParticleLinkCollection{}; +auto assoc = mcRecoAssocs.create(); // create an link; assoc.setFrom(mcParticle); assoc.setTo(recoParticle); assoc.setWeight(1.0); // This is also the default value! @@ -95,7 +95,7 @@ auto recoP = assoc.get(); auto weight = assoc.getWeight(); ``` -It is also possible to access the elements of an association via an index based +It is also possible to access the elements of an link via an index based `get` (similar to `std::tuple`). In this case `0` corresponds to `getFrom`, `1` corresponds to `getTo` and `2` corresponds to the weight. The main purpose of this feature is to enable structured bindings: @@ -105,21 +105,21 @@ const auto& [mcP, recoP, weight] = assoc; ``` The above three examples are three equivalent ways of retrieving the same things -from an `Association`. **The templated `get` and `set` methods are only available +from an `Link`. **The templated `get` and `set` methods are only available if `FromT` and `ToT` are not the same type** and will lead to a compilation error otherwise. -### Enabling I/O capabilities for `Association`s +### Enabling I/O capabilities for `Link`s -`Association`s do not have I/O support out of the box. This has to be enabled via -the `PODIO_DECLARE_ASSOCIATION` macro (defined in the `AssociationCollection.h` -header). If you simply want to be able to read / write `Association`s in a +`Link`s do not have I/O support out of the box. This has to be enabled via +the `PODIO_DECLARE_LINK` macro (defined in the `LinkCollection.h` +header). If you simply want to be able to read / write `Link`s in a standalone executable, it is enough to use this macro somewhere in the -executable, e.g. to enable I/O capabilities for the `MCRecoParticleAssociation`s +executable, e.g. to enable I/O capabilities for the `MCRecoParticleLink`s used above this would look like: ```cpp -PODIO_DECLARE_ASSOCIATION(edm4hep::MCParticle, edm4hep::ReconstructedParticle) +PODIO_DECLARE_LINK(edm4hep::MCParticle, edm4hep::ReconstructedParticle) ``` The macro will also enable SIO support if the `PODIO_ENABLE_SIO=1` is passed to @@ -127,7 +127,7 @@ the compiler. This is done by default when linking against the `podio::podioSioIO` library in CMake. For enabling I/O support for shared datamodel libraries, it is necessary to have -all the necessary combinations of types declared via `PODIO_DECLARE_ASSOCIATION` +all the necessary combinations of types declared via `PODIO_DECLARE_LINK` and have that compiled into the library. This is necessary if you want to use the python bindings, since they rely on dynamically loading the datamodel libraries. @@ -146,54 +146,54 @@ be obtained by generating them via the yaml snippet above and sprinkling some ### File structure -The user facing `"podio/AssociationCollection.h"` header essentially just -defines the `PODIO_DECLARE_ASSOCIATION` macro (depending on whether SIO support +The user facing `"podio/LinkCollection.h"` header essentially just +defines the `PODIO_DECLARE_LINK` macro (depending on whether SIO support is desired and possible or not). All the actual implementation is done in the following files: -- [`"podio/detail/AssociationCollectionImpl.h"`](https://github.com/AIDASoft/podio/blob/master/include/podio/detail/AssociationCollectionImpl.h): +- [`"podio/detail/LinkCollectionImpl.h"`](https://github.com/AIDASoft/podio/blob/master/include/podio/detail/LinkCollectionImpl.h): for the collection functionality -- [`"podio/detail/Association.h"`](https://github.com/AIDASoft/podio/blob/master/include/podio/detail/Association.h): - for the functionality of single association -- [`"podio/detail/AssociationCollectionIterator.h"`](https://github.com/AIDASoft/podio/blob/master/include/podio/detail/AssociationCollectionIterator.h): +- [`"podio/detail/Link.h"`](https://github.com/AIDASoft/podio/blob/master/include/podio/detail/Link.h): + for the functionality of single link +- [`"podio/detail/LinkCollectionIterator.h"`](https://github.com/AIDASoft/podio/blob/master/include/podio/detail/LinkCollectionIterator.h): for the collection iterator functionality -- [`"podio/detail/AssociationObj.h"`](https://github.com/AIDASoft/podio/blob/master/include/podio/detail/AssociationObj.h): +- [`"podio/detail/LinkObj.h"`](https://github.com/AIDASoft/podio/blob/master/include/podio/detail/LinkObj.h): for the object layer functionality - - [`"podio/detail/AssociationCollectionData.h"`](https://github.com/AIDASoft/podio/blob/master/include/podio/detail/AssociationCollectionData.h): + - [`"podio/detail/LinkCollectionData.h"`](https://github.com/AIDASoft/podio/blob/master/include/podio/detail/LinkCollectionData.h): for the collection data functionality -- [`"podio/detail/AssociationFwd.h"`](https://github.com/AIDASoft/podio/blob/master/include/podio/detail/AssociationFwd.h): +- [`"podio/detail/LinkFwd.h"`](https://github.com/AIDASoft/podio/blob/master/include/podio/detail/LinkFwd.h): for some type helper functionality and some forward declarations that are used throughout the other headers -- [`"podio/detail/AssociationSIOBlock.h"`](https://github.com/AIDASoft/podio/blob/master/include/podio/detail/AssociationSIOBlock.h): +- [`"podio/detail/LinkSIOBlock.h"`](https://github.com/AIDASoft/podio/blob/master/include/podio/detail/LinkSIOBlock.h): for defining the SIOBlocks that are necessary to use SIO -As is visible from this structure, we did not introduce an `AssociationData` +As is visible from this structure, we did not introduce an `LinkData` class, since that would effectively just be a `float` wrapped inside a `struct`. -### Default and `Mutable` `Association` classes +### Default and `Mutable` `Link` classes -A quick look into the `AssociationFwd.h` header will reveal that the default and -`Mutable` `Association` classes are in fact just partial specialization of the -`AssociationT` class that takes a `bool Mutable` as third template argument. The +A quick look into the `LinkFwd.h` header will reveal that the default and +`Mutable` `Link` classes are in fact just partial specialization of the +`LinkT` class that takes a `bool Mutable` as third template argument. The same approach is also followed by the `AssocationCollectionIterator`s: ```cpp template -class AssociationT; +class LinkT; template -using Association = AssociationT; +using Link = LinkT; template -using MutableAssociation = AssociationT; +using MutableLink = LinkT; ``` Throughout the implementation it is assumed that `FromT` and `ToT` always are the default handle types. This is ensured through `static_assert`s in the -`AssociationCollection` to make sure it can only be instantiated with those. The +`LinkCollection` to make sure it can only be instantiated with those. The `GetDefaultHandleType` helper templates are used to retrieve the correct type from any `FromT` regardless of whether it is a mutable or a default handle type -With this in mind, effectively all mutating operations on `Association`s are +With this in mind, effectively all mutating operations on `Link`s are defined using [*SFINAE*](https://en.cppreference.com/w/cpp/language/sfinae) using the following template structure (taking here `setFrom` as an example) diff --git a/include/podio/AssociationCollection.h b/include/podio/AssociationCollection.h deleted file mode 100644 index 57eb2d449..000000000 --- a/include/podio/AssociationCollection.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef PODIO_ASSOCIATIONCOLLECTION_H -#define PODIO_ASSOCIATIONCOLLECTION_H - -#include "podio/detail/AssociationCollectionImpl.h" - -// Preprocessor helper macros for concatenating tokens at preprocessing times -// Necessary because we use __COUNTER__ below for unique names of static -// variables for values returned by registration function calls -#define PODIO_PP_CONCAT_IMPL(x, y) x##y -#define PODIO_PP_CONCAT(x, y) PODIO_PP_CONCAT_IMPL(x, y) - -#ifndef PODIO_ENABLE_SIO - #define PODIO_ENABLE_SIO 0 -#endif - -#if PODIO_ENABLE_SIO && __has_include("podio/detail/AssociationSIOBlock.h") - #include "podio/detail/AssociationSIOBlock.h" - /// Main macro for declaring associations. Takes care of the following things: - /// - Registering the necessary buffer creation functionality with the - /// CollectionBufferFactory. - /// - Registering the necessary SIOBlock with the SIOBlock factory - #define PODIO_DECLARE_ASSOCIATION(FromT, ToT) \ - const static auto PODIO_PP_CONCAT(REGISTERED_ASSOCIATION_, __COUNTER__) = \ - podio::detail::registerAssociationCollection( \ - podio::detail::associationCollTypeName()); \ - const static auto PODIO_PP_CONCAT(ASSOCIATION_SIO_BLOCK_, __COUNTER__) = podio::AssociationSIOBlock{}; -#else - /// Main macro for declaring associations. Takes care of the following things: - /// - Registering the necessary buffer creation functionality with the - /// CollectionBufferFactory. - #define PODIO_DECLARE_ASSOCIATION(FromT, ToT) \ - const static auto PODIO_PP_CONCAT(REGISTERED_ASSOCIATION_, __COUNTER__) = \ - podio::detail::registerAssociationCollection( \ - podio::detail::associationCollTypeName()); -#endif - -#endif // PODIO_ASSOCIATIONCOLLECTION_H diff --git a/include/podio/LinkCollection.h b/include/podio/LinkCollection.h new file mode 100644 index 000000000..82db26d5a --- /dev/null +++ b/include/podio/LinkCollection.h @@ -0,0 +1,35 @@ +#ifndef PODIO_LINKCOLLECTION_H +#define PODIO_LINKCOLLECTION_H + +#include "podio/detail/LinkCollectionImpl.h" + +// Preprocessor helper macros for concatenating tokens at preprocessing times +// Necessary because we use __COUNTER__ below for unique names of static +// variables for values returned by registration function calls +#define PODIO_PP_CONCAT_IMPL(x, y) x##y +#define PODIO_PP_CONCAT(x, y) PODIO_PP_CONCAT_IMPL(x, y) + +#ifndef PODIO_ENABLE_SIO + #define PODIO_ENABLE_SIO 0 +#endif + +#if PODIO_ENABLE_SIO && __has_include("podio/detail/LinkSIOBlock.h") + #include "podio/detail/LinkSIOBlock.h" + /// Main macro for declaring links. Takes care of the following things: + /// - Registering the necessary buffer creation functionality with the + /// CollectionBufferFactory. + /// - Registering the necessary SIOBlock with the SIOBlock factory + #define PODIO_DECLARE_LINK(FromT, ToT) \ + const static auto PODIO_PP_CONCAT(REGISTERED_LINK_, __COUNTER__) = \ + podio::detail::registerLinkCollection(podio::detail::linkCollTypeName()); \ + const static auto PODIO_PP_CONCAT(LINK_SIO_BLOCK_, __COUNTER__) = podio::LinkSIOBlock{}; +#else + /// Main macro for declaring links. Takes care of the following things: + /// - Registering the necessary buffer creation functionality with the + /// CollectionBufferFactory. + #define PODIO_DECLARE_LINK(FromT, ToT) \ + const static auto PODIO_PP_CONCAT(REGISTERED_LINK_, __COUNTER__) = \ + podio::detail::registerLinkCollection(podio::detail::linkCollTypeName()); +#endif + +#endif // PODIO_LINKCOLLECTION_H diff --git a/include/podio/detail/Association.h b/include/podio/detail/Association.h index 564dc3f9c..e69de29bb 100644 --- a/include/podio/detail/Association.h +++ b/include/podio/detail/Association.h @@ -1,289 +0,0 @@ -#ifndef PODIO_DETAIL_ASSOCIATION_H -#define PODIO_DETAIL_ASSOCIATION_H - -#include "podio/detail/AssociationFwd.h" -#include "podio/detail/AssociationObj.h" -#include "podio/utilities/MaybeSharedPtr.h" -#include "podio/utilities/TypeHelpers.h" - -#ifdef PODIO_JSON_OUTPUT - #include "nlohmann/json.hpp" -#endif - -#include -#include // std::swap - -namespace podio { - -/// Generalized Association type for both Mutable and immutable (default) -/// versions. User facing clases with the expected naming scheme are defined via -/// template aliases are defined just below -template -class AssociationT { - // The typedefs in AssociationFwd.h should make sure that at this point - // Mutable classes are stripped, i.e. the user should never be able to trigger - // these! - static_assert(std::is_same_v, FromT>, - "Associations need to be instantiated with the default types!"); - static_assert(std::is_same_v, ToT>, - "Associations need to be instantiated with the default types!"); - - using AssociationObjT = AssociationObj; - friend AssociationCollection; - friend AssociationCollectionIteratorT; - friend AssociationT; - - /// Helper member variable to check whether FromU and ToU can be used for this - /// Association. We need this to make SFINAE trigger in some cases below - template - constexpr static bool sameTypes = std::is_same_v && std::is_same_v; - - /// Variable template to for determining whether T is either FromT or ToT. - /// Mainly defined for convenience - template - static constexpr bool isFromOrToT = detail::isInTuple>; - - /// Variable template to for determining whether T is either FromT or ToT or - /// any of their mutable versions. - template - static constexpr bool isMutableFromOrToT = - detail::isInTuple, detail::GetMutableHandleType>>; - -public: - using mutable_type = podio::MutableAssociation; - using value_type = podio::Association; - using collection_type = podio::AssociationCollection; - - /// Constructor - AssociationT() : m_obj(new AssociationObjT{}, podio::utils::MarkOwned) { - } - - /// Constructor with weight - AssociationT(float weight) : m_obj(new AssociationObjT{}, podio::utils::MarkOwned) { - m_obj->weight = weight; - } - - /// Copy constructor - AssociationT(const AssociationT& other) = default; - - /// Assignment operator - AssociationT& operator=(AssociationT other) { - swap(*this, other); - return *this; - } - - /// Implicit conversion of mutable to immutable associations - template >> - operator AssociationT() const { - return AssociationT(m_obj); - } - - /// Create a mutable deep-copy with identical relations - template >> - MutableAssociation clone(bool cloneRelations = true) const { - auto tmp = new AssociationObjT(podio::ObjectID{}, m_obj->weight); - if (cloneRelations) { - if (m_obj->m_from) { - tmp->m_from = new FromT(*m_obj->m_from); - } - if (m_obj->m_to) { - tmp->m_to = new ToT(*m_obj->m_to); - } - } - return MutableAssociation(podio::utils::MaybeSharedPtr(tmp, podio::utils::MarkOwned)); - } - - template > - static Association makeEmpty() { - return {nullptr}; - } - - /// Destructor - ~AssociationT() = default; - - /// Get the weight of the association - float getWeight() const { - return m_obj->weight; - } - - /// Set the weight of the association - template > - void setWeight(float value) { - m_obj->weight = value; - } - - /// Access the related-from object - const FromT getFrom() const { - if (!m_obj->m_from) { - return FromT::makeEmpty(); - } - return FromT(*(m_obj->m_from)); - } - - /// Set the related-from object - template , FromT>>> - void setFrom(FromU value) { - delete m_obj->m_from; - m_obj->m_from = new detail::GetDefaultHandleType(value); - } - - /// Access the related-to object - const ToT getTo() const { - if (!m_obj->m_to) { - return ToT::makeEmpty(); - } - return ToT(*(m_obj->m_to)); - } - - /// Set the related-to object - template , ToT>>> - void setTo(ToU value) { - delete m_obj->m_to; - m_obj->m_to = new detail::GetDefaultHandleType(value); - } - - /// Templated version for getting an element of the association by type. Only - /// available for Associations where FromT and ToT are **not the same type**, - /// and if the requested type is actually part of the Association. It is only - /// possible to get the immutable types from this. Will result in a - /// compilation error if any of these conditions is not met. - /// - /// @tparam T the desired type - /// @returns T the element of the Association - template && isFromOrToT>> - T get() const { - if constexpr (std::is_same_v) { - return getFrom(); - } else { - return getTo(); - } - } - - /// Tuple like index based access to the elements of the Association. Returns - /// only immutable types of the associations. This method enables structured - /// bindings for Associations. - /// - /// @tparam Index an index (smaller than 3) to access an element of the Association - /// @returns Depending on the value of Index: - /// - 0: The From element of the Association - /// - 1: The To element of the Association - /// - 2: The weight of the Association - template > - auto get() const { - if constexpr (Index == 0) { - return getFrom(); - } else if constexpr (Index == 1) { - return getTo(); - } else { - return getWeight(); - } - } - - /// Templated version for setting an element of the association by type. Only - /// available for Associations where FromT and ToT are **not the same type**, - /// and if the requested type is actually part of the Association. Will result - /// in a compilation error if any of these conditions is not met. - /// - /// @tparam T type of value (**infered!**) - /// @param value the element to set for this association. - template && isMutableFromOrToT>> - void set(T value) { - if constexpr (std::is_same_v) { - setFrom(std::move(value)); - } else { - setTo(std::move(value)); - } - } - - /// check whether the object is actually available - bool isAvailable() const { - return m_obj; - } - - /// disconnect from Association instance - void unlink() { - m_obj = podio::utils::MaybeSharedPtr(nullptr); - } - - /// Get the ObjectID - podio::ObjectID getObjectID() const { - if (m_obj) { - return m_obj->id; - } - return podio::ObjectID{}; - } - - podio::ObjectID id() const { - return getObjectID(); - } - - bool operator==(const AssociationT& other) const { - return m_obj == other.m_obj; - } - - bool operator!=(const AssociationT& other) const { - return !(*this == other); - } - - template >> - bool operator==(const AssociationT& other) const { - return m_obj == other.m_obj; - } - - template >> - bool operator!=(const AssociationT& other) const { - return !(*this == other); - } - - bool operator<(const AssociationT& other) const { - return m_obj < other.m_obj; - } - - friend void swap(AssociationT& a, AssociationT& b) { - using std::swap; - swap(a.m_obj, b.m_obj); // swap out the internal pointers - } - -private: - /// Constructor from existing AssociationObj - explicit AssociationT(podio::utils::MaybeSharedPtr obj) : m_obj(std::move(obj)) { - } - - template > - AssociationT(AssociationObjT* obj) : m_obj(podio::utils::MaybeSharedPtr(obj)) { - } - - podio::utils::MaybeSharedPtr m_obj{nullptr}; -}; // namespace podio - -template -std::ostream& operator<<(std::ostream& os, const Association& assoc) { - if (!assoc.isAvailable()) { - return os << "[not available]"; - } - - return os << " id: " << assoc.id() << '\n' - << " weight: " << assoc.getWeight() << '\n' - << " from: " << assoc.getFrom().id() << '\n' - << " to: " << assoc.getTo().id() << '\n'; -} - -#ifdef PODIO_JSON_OUTPUT -template -void to_json(nlohmann::json& j, const Association& assoc) { - j = nlohmann::json{{"weight", assoc.getWeight()}}; - - j["from"] = nlohmann::json{{"collectionID", assoc.getFrom().getObjectID().collectionID}, - {"index", assoc.getFrom().getObjectID().index}}; - - j["to"] = nlohmann::json{{"collectionID", assoc.getTo().getObjectID().collectionID}, - {"index", assoc.getTo().getObjectID().index}}; -} -#endif - -} // namespace podio - -#endif // PODIO_DETAIL_ASSOCIATION_H diff --git a/include/podio/detail/AssociationCollectionIterator.h b/include/podio/detail/AssociationCollectionIterator.h deleted file mode 100644 index 3a9d460f5..000000000 --- a/include/podio/detail/AssociationCollectionIterator.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef PODIO_DETAIL_ASSOCIATIONCOLLECTIONITERATOR_H -#define PODIO_DETAIL_ASSOCIATIONCOLLECTIONITERATOR_H - -#include "podio/detail/AssociationFwd.h" -#include "podio/utilities/MaybeSharedPtr.h" - -namespace podio { -template -class AssociationCollectionIteratorT { - using AssocT = AssociationT; - using AssociationObjT = AssociationObj; - -public: - AssociationCollectionIteratorT(size_t index, const AssociationObjPointerContainer* coll) : - m_index(index), m_object(podio::utils::MaybeSharedPtr{nullptr}), m_collection(coll) { - } - - AssociationCollectionIteratorT(const AssociationCollectionIteratorT&) = delete; - AssociationCollectionIteratorT& operator=(const AssociationCollectionIteratorT&) = delete; - - bool operator!=(const AssociationCollectionIteratorT& other) const { - return m_index != other.m_index; // TODO: may not be complete - } - - AssocT operator*() { - m_object.m_obj = podio::utils::MaybeSharedPtr((*m_collection)[m_index]); - return m_object; - } - - AssocT* operator->() { - m_object.m_obj = podio::utils::MaybeSharedPtr((*m_collection)[m_index]); - return &m_object; - } - - AssociationCollectionIteratorT& operator++() { - ++m_index; - return *this; - } - -private: - size_t m_index; - AssocT m_object; - const AssociationObjPointerContainer* m_collection; -}; -} // namespace podio - -#endif // PODIO_DETAIL_ASSOCIATIONCOLLECTIONITERATOR_H diff --git a/include/podio/detail/AssociationFwd.h b/include/podio/detail/AssociationFwd.h deleted file mode 100644 index c3b74930d..000000000 --- a/include/podio/detail/AssociationFwd.h +++ /dev/null @@ -1,101 +0,0 @@ -#ifndef PODIO_DETAIL_ASSOCIATIONFWD_H -#define PODIO_DETAIL_ASSOCIATIONFWD_H - -#include "podio/utilities/TypeHelpers.h" - -#include -#include -#include -#include -#include - -namespace podio { -namespace detail { - - /// Get the collection type name for an AssociationCollection - /// - /// @tparam FromT the From type of the association - /// @tparam ToT the To type of the association - /// @returns a type string that is a valid c++ template instantiation - template - inline const std::string_view associationCollTypeName() { - const static std::string typeName = - std::string("podio::AssociationCollection<") + FromT::typeName + "," + ToT::typeName + ">"; - return std::string_view{typeName}; - } - - /// Get the value type name for an AssociationCollection - /// - /// @tparam FromT the From type of the association - /// @tparam ToT the To type of the association - /// @returns a type string that is a valid c++ template instantiation - template - inline const std::string_view associationTypeName() { - const static std::string typeName = - std::string("podio::Association<") + FromT::typeName + "," + ToT::typeName + ">"; - return std::string_view{typeName}; - } - - /// Get an SIO friendly type name for an AssociationCollection (necessary for - /// registration in the SIOBlockFactory) - /// - /// @tparam FromT the From type of the association - /// @tparam ToT the To type of - /// the association - /// @returns a string that uniquely identifies this combination - /// of From and To types - template - inline const std::string& associationSIOName() { - static auto n = std::string("ASSOCIATION_FROM_") + FromT::typeName + "_TO_" + ToT::typeName; - std::replace(n.begin(), n.end(), ':', '_'); - return n; - } -} // namespace detail - -// Forward declarations and typedefs used throughout the whole Association -// business -template -class AssociationObj; - -template -using AssociationObjPointerContainer = std::deque*>; - -using AssociationDataContainer = std::vector; - -template -class AssociationT; - -template -using Association = AssociationT, detail::GetDefaultHandleType, false>; - -template -using MutableAssociation = AssociationT, detail::GetDefaultHandleType, true>; - -template -class AssociationCollection; - -template -class AssociationCollectionData; - -template -class AssociationCollectionIteratorT; - -template -using AssociationCollectionIterator = AssociationCollectionIteratorT; - -template -using AssociationMutableCollectionIterator = AssociationCollectionIteratorT; - -} // namespace podio - -namespace std { -/// Specialization for enabling structure bindings for Associations -template -struct tuple_size> : std::integral_constant {}; - -/// Specialization for enabling structure bindings for Associations -template -struct tuple_element> : tuple_element> {}; -} // namespace std - -#endif // PODIO_DETAIL_ASSOCIATIONFWD_H diff --git a/include/podio/detail/Link.h b/include/podio/detail/Link.h new file mode 100644 index 000000000..a10fe6ab1 --- /dev/null +++ b/include/podio/detail/Link.h @@ -0,0 +1,289 @@ +#ifndef PODIO_DETAIL_LINK_H +#define PODIO_DETAIL_LINK_H + +#include "podio/detail/LinkFwd.h" +#include "podio/detail/LinkObj.h" +#include "podio/utilities/MaybeSharedPtr.h" +#include "podio/utilities/TypeHelpers.h" + +#ifdef PODIO_JSON_OUTPUT + #include "nlohmann/json.hpp" +#endif + +#include +#include // std::swap + +namespace podio { + +/// Generalized Link type for both Mutable and immutable (default) +/// versions. User facing clases with the expected naming scheme are defined via +/// template aliases are defined just below +template +class LinkT { + // The typedefs in LinkFwd.h should make sure that at this point + // Mutable classes are stripped, i.e. the user should never be able to trigger + // these! + static_assert(std::is_same_v, FromT>, + "Links need to be instantiated with the default types!"); + static_assert(std::is_same_v, ToT>, + "Links need to be instantiated with the default types!"); + + using LinkObjT = LinkObj; + friend LinkCollection; + friend LinkCollectionIteratorT; + friend LinkT; + + /// Helper member variable to check whether FromU and ToU can be used for this + /// Link. We need this to make SFINAE trigger in some cases below + template + constexpr static bool sameTypes = std::is_same_v && std::is_same_v; + + /// Variable template to for determining whether T is either FromT or ToT. + /// Mainly defined for convenience + template + static constexpr bool isFromOrToT = detail::isInTuple>; + + /// Variable template to for determining whether T is either FromT or ToT or + /// any of their mutable versions. + template + static constexpr bool isMutableFromOrToT = + detail::isInTuple, detail::GetMutableHandleType>>; + +public: + using mutable_type = podio::MutableLink; + using value_type = podio::Link; + using collection_type = podio::LinkCollection; + + /// Constructor + LinkT() : m_obj(new LinkObjT{}, podio::utils::MarkOwned) { + } + + /// Constructor with weight + LinkT(float weight) : m_obj(new LinkObjT{}, podio::utils::MarkOwned) { + m_obj->weight = weight; + } + + /// Copy constructor + LinkT(const LinkT& other) = default; + + /// Assignment operator + LinkT& operator=(LinkT other) { + swap(*this, other); + return *this; + } + + /// Implicit conversion of mutable to immutable links + template >> + operator LinkT() const { + return LinkT(m_obj); + } + + /// Create a mutable deep-copy with identical relations + template >> + MutableLink clone(bool cloneRelations = true) const { + auto tmp = new LinkObjT(podio::ObjectID{}, m_obj->weight); + if (cloneRelations) { + if (m_obj->m_from) { + tmp->m_from = new FromT(*m_obj->m_from); + } + if (m_obj->m_to) { + tmp->m_to = new ToT(*m_obj->m_to); + } + } + return MutableLink(podio::utils::MaybeSharedPtr(tmp, podio::utils::MarkOwned)); + } + + template > + static Link makeEmpty() { + return {nullptr}; + } + + /// Destructor + ~LinkT() = default; + + /// Get the weight of the link + float getWeight() const { + return m_obj->weight; + } + + /// Set the weight of the link + template > + void setWeight(float value) { + m_obj->weight = value; + } + + /// Access the related-from object + const FromT getFrom() const { + if (!m_obj->m_from) { + return FromT::makeEmpty(); + } + return FromT(*(m_obj->m_from)); + } + + /// Set the related-from object + template , FromT>>> + void setFrom(FromU value) { + delete m_obj->m_from; + m_obj->m_from = new detail::GetDefaultHandleType(value); + } + + /// Access the related-to object + const ToT getTo() const { + if (!m_obj->m_to) { + return ToT::makeEmpty(); + } + return ToT(*(m_obj->m_to)); + } + + /// Set the related-to object + template , ToT>>> + void setTo(ToU value) { + delete m_obj->m_to; + m_obj->m_to = new detail::GetDefaultHandleType(value); + } + + /// Templated version for getting an element of the link by type. Only + /// available for Links where FromT and ToT are **not the same type**, + /// and if the requested type is actually part of the Link. It is only + /// possible to get the immutable types from this. Will result in a + /// compilation error if any of these conditions is not met. + /// + /// @tparam T the desired type + /// @returns T the element of the Link + template && isFromOrToT>> + T get() const { + if constexpr (std::is_same_v) { + return getFrom(); + } else { + return getTo(); + } + } + + /// Tuple like index based access to the elements of the Link. Returns + /// only immutable types of the links. This method enables structured + /// bindings for Links. + /// + /// @tparam Index an index (smaller than 3) to access an element of the Link + /// @returns Depending on the value of Index: + /// - 0: The From element of the Link + /// - 1: The To element of the Link + /// - 2: The weight of the Link + template > + auto get() const { + if constexpr (Index == 0) { + return getFrom(); + } else if constexpr (Index == 1) { + return getTo(); + } else { + return getWeight(); + } + } + + /// Templated version for setting an element of the link by type. Only + /// available for Links where FromT and ToT are **not the same type**, + /// and if the requested type is actually part of the Link. Will result + /// in a compilation error if any of these conditions is not met. + /// + /// @tparam T type of value (**infered!**) + /// @param value the element to set for this link. + template && isMutableFromOrToT>> + void set(T value) { + if constexpr (std::is_same_v) { + setFrom(std::move(value)); + } else { + setTo(std::move(value)); + } + } + + /// check whether the object is actually available + bool isAvailable() const { + return m_obj; + } + + /// disconnect from Link instance + void unlink() { + m_obj = podio::utils::MaybeSharedPtr(nullptr); + } + + /// Get the ObjectID + podio::ObjectID getObjectID() const { + if (m_obj) { + return m_obj->id; + } + return podio::ObjectID{}; + } + + podio::ObjectID id() const { + return getObjectID(); + } + + bool operator==(const LinkT& other) const { + return m_obj == other.m_obj; + } + + bool operator!=(const LinkT& other) const { + return !(*this == other); + } + + template >> + bool operator==(const LinkT& other) const { + return m_obj == other.m_obj; + } + + template >> + bool operator!=(const LinkT& other) const { + return !(*this == other); + } + + bool operator<(const LinkT& other) const { + return m_obj < other.m_obj; + } + + friend void swap(LinkT& a, LinkT& b) { + using std::swap; + swap(a.m_obj, b.m_obj); // swap out the internal pointers + } + +private: + /// Constructor from existing LinkObj + explicit LinkT(podio::utils::MaybeSharedPtr obj) : m_obj(std::move(obj)) { + } + + template > + LinkT(LinkObjT* obj) : m_obj(podio::utils::MaybeSharedPtr(obj)) { + } + + podio::utils::MaybeSharedPtr m_obj{nullptr}; +}; // namespace podio + +template +std::ostream& operator<<(std::ostream& os, const Link& assoc) { + if (!assoc.isAvailable()) { + return os << "[not available]"; + } + + return os << " id: " << assoc.id() << '\n' + << " weight: " << assoc.getWeight() << '\n' + << " from: " << assoc.getFrom().id() << '\n' + << " to: " << assoc.getTo().id() << '\n'; +} + +#ifdef PODIO_JSON_OUTPUT +template +void to_json(nlohmann::json& j, const Link& assoc) { + j = nlohmann::json{{"weight", assoc.getWeight()}}; + + j["from"] = nlohmann::json{{"collectionID", assoc.getFrom().getObjectID().collectionID}, + {"index", assoc.getFrom().getObjectID().index}}; + + j["to"] = nlohmann::json{{"collectionID", assoc.getTo().getObjectID().collectionID}, + {"index", assoc.getTo().getObjectID().index}}; +} +#endif + +} // namespace podio + +#endif // PODIO_DETAIL_LINK_H diff --git a/include/podio/detail/AssociationCollectionData.h b/include/podio/detail/LinkCollectionData.h similarity index 82% rename from include/podio/detail/AssociationCollectionData.h rename to include/podio/detail/LinkCollectionData.h index a6c991e72..76190cac3 100644 --- a/include/podio/detail/AssociationCollectionData.h +++ b/include/podio/detail/LinkCollectionData.h @@ -1,8 +1,8 @@ -#ifndef PODIO_DETAIL_ASSOCIATIONCOLLECTIONDATA_H -#define PODIO_DETAIL_ASSOCIATIONCOLLECTIONDATA_H +#ifndef PODIO_DETAIL_LINKCOLLECTIONDATA_H +#define PODIO_DETAIL_LINKCOLLECTIONDATA_H -#include "podio/detail/AssociationFwd.h" -#include "podio/detail/AssociationObj.h" +#include "podio/detail/LinkFwd.h" +#include "podio/detail/LinkObj.h" #include "podio/CollectionBase.h" #include "podio/CollectionBuffers.h" @@ -15,18 +15,18 @@ namespace podio { template -class AssociationCollectionData { +class LinkCollectionData { public: - AssociationObjPointerContainer entries{}; + LinkObjPointerContainer entries{}; - AssociationCollectionData() : - m_rel_from(new std::vector()), m_rel_to(new std::vector()), m_data(new AssociationDataContainer()) { + LinkCollectionData() : + m_rel_from(new std::vector()), m_rel_to(new std::vector()), m_data(new LinkDataContainer()) { m_refCollections.reserve(2); m_refCollections.emplace_back(std::make_unique>()); m_refCollections.emplace_back(std::make_unique>()); } - AssociationCollectionData(podio::CollectionReadBuffers buffers, bool isSubsetColl) : + LinkCollectionData(podio::CollectionReadBuffers buffers, bool isSubsetColl) : m_rel_from(new std::vector()), m_rel_to(new std::vector()), m_refCollections(std::move(*buffers.references)) { @@ -35,11 +35,11 @@ class AssociationCollectionData { } } - AssociationCollectionData(const AssociationCollectionData&) = delete; - AssociationCollectionData& operator=(const AssociationCollectionData&) = delete; - AssociationCollectionData(AssociationCollectionData&&) = default; - AssociationCollectionData& operator=(AssociationCollectionData&&) = default; - ~AssociationCollectionData() = default; + LinkCollectionData(const LinkCollectionData&) = delete; + LinkCollectionData& operator=(const LinkCollectionData&) = delete; + LinkCollectionData(LinkCollectionData&&) = default; + LinkCollectionData& operator=(LinkCollectionData&&) = default; + ~LinkCollectionData() = default; podio::CollectionWriteBuffers getCollectionBuffers(bool isSubsetColl) { return {isSubsetColl ? nullptr : (void*)&m_data, (void*)m_data.get(), &m_refCollections, &m_vecInfo}; @@ -118,7 +118,7 @@ class AssociationCollectionData { void prepareAfterRead(uint32_t collectionID) { int index = 0; for (const auto data : *m_data) { - auto obj = new AssociationObj({index++, collectionID}, data); + auto obj = new LinkObj({index++, collectionID}, data); entries.emplace_back(obj); } @@ -129,9 +129,9 @@ class AssociationCollectionData { if (isSubsetColl) { for (const auto& id : *m_refCollections[0]) { podio::CollectionBase* coll{nullptr}; - AssociationObj* obj{nullptr}; + LinkObj* obj{nullptr}; if (collectionProvider->get(id.collectionID, coll)) { - auto* tmp_coll = static_cast*>(coll); + auto* tmp_coll = static_cast*>(coll); obj = tmp_coll->m_storage.entries[id.index]; } entries.push_back(obj); @@ -194,9 +194,9 @@ class AssociationCollectionData { // I/O related buffers (as far as necessary) podio::CollRefCollection m_refCollections{}; podio::VectorMembersInfo m_vecInfo{}; - std::unique_ptr m_data{nullptr}; + std::unique_ptr m_data{nullptr}; }; } // namespace podio -#endif // PODIO_DETAIL_ASSOCIATIONCOLLECTIONDATA_H +#endif // PODIO_DETAIL_LINKCOLLECTIONDATA_H diff --git a/include/podio/detail/AssociationCollectionImpl.h b/include/podio/detail/LinkCollectionImpl.h similarity index 79% rename from include/podio/detail/AssociationCollectionImpl.h rename to include/podio/detail/LinkCollectionImpl.h index 3123b9707..53367453a 100644 --- a/include/podio/detail/AssociationCollectionImpl.h +++ b/include/podio/detail/LinkCollectionImpl.h @@ -1,11 +1,11 @@ -#ifndef PODIO_DETAIL_ASSOCIATIONCOLLECTIONIMPL_H -#define PODIO_DETAIL_ASSOCIATIONCOLLECTIONIMPL_H +#ifndef PODIO_DETAIL_LINKCOLLECTIONIMPL_H +#define PODIO_DETAIL_LINKCOLLECTIONIMPL_H -#include "podio/detail/Association.h" -#include "podio/detail/AssociationCollectionData.h" -#include "podio/detail/AssociationCollectionIterator.h" -#include "podio/detail/AssociationFwd.h" -#include "podio/detail/AssociationObj.h" +#include "podio/detail/Link.h" +#include "podio/detail/LinkCollectionData.h" +#include "podio/detail/LinkCollectionIterator.h" +#include "podio/detail/LinkFwd.h" +#include "podio/detail/LinkObj.h" #include "podio/CollectionBase.h" #include "podio/CollectionBufferFactory.h" @@ -30,47 +30,47 @@ namespace podio { template -class AssociationCollection : public podio::CollectionBase { +class LinkCollection : public podio::CollectionBase { static_assert(std::is_same_v>, - "Associations need to be instantiated with the default types!"); + "Links need to be instantiated with the default types!"); static_assert(std::is_same_v>, - "Associations need to be instantiated with the default types!"); + "Links need to be instantiated with the default types!"); // convenience typedefs - using CollectionDataT = podio::AssociationCollectionData; + using CollectionDataT = podio::LinkCollectionData; public: - using value_type = Association; - using mutable_type = MutableAssociation; - using const_iterator = AssociationCollectionIterator; - using iterator = AssociationMutableCollectionIterator; + using value_type = Link; + using mutable_type = MutableLink; + using const_iterator = LinkCollectionIterator; + using iterator = LinkMutableCollectionIterator; using difference_type = ptrdiff_t; using size_type = size_t; - AssociationCollection() = default; + LinkCollection() = default; - AssociationCollection(CollectionDataT&& data, bool isSubsetColl) : + LinkCollection(CollectionDataT&& data, bool isSubsetColl) : m_isSubsetColl(isSubsetColl), m_collectionID(0), m_storage(std::move(data)) { } // Move-only type - AssociationCollection(const AssociationCollection&) = delete; - AssociationCollection& operator=(const AssociationCollection&) = delete; - AssociationCollection(AssociationCollection&&) = default; - AssociationCollection& operator=(AssociationCollection&&) = default; + LinkCollection(const LinkCollection&) = delete; + LinkCollection& operator=(const LinkCollection&) = delete; + LinkCollection(LinkCollection&&) = default; + LinkCollection& operator=(LinkCollection&&) = default; - ~AssociationCollection() { + ~LinkCollection() { // Need to tell the storage how to clean up m_storage.clear(m_isSubsetColl); } - /// Append a new association to the collection and return this object + /// Append a new link to the collection and return this object mutable_type create() { if (m_isSubsetColl) { throw std::logic_error("Cannot create new elements on a subset collection"); } - auto obj = m_storage.entries.emplace_back(new AssociationObj()); + auto obj = m_storage.entries.emplace_back(new LinkObj()); obj->id = {int(m_storage.entries.size() - 1), m_collectionID}; return mutable_type(podio::utils::MaybeSharedPtr(obj)); } @@ -180,11 +180,11 @@ class AssociationCollection : public podio::CollectionBase { } const std::string_view getTypeName() const override { - return podio::detail::associationCollTypeName(); + return podio::detail::linkCollTypeName(); } const std::string_view getValueTypeName() const override { - return podio::detail::associationTypeName(); + return podio::detail::linkTypeName(); } const std::string_view getDataTypeName() const override { @@ -279,7 +279,7 @@ class AssociationCollection : public podio::CollectionBase { }; template -std::ostream& operator<<(std::ostream& o, const AssociationCollection& v) { +std::ostream& operator<<(std::ostream& o, const LinkCollection& v) { const auto old_flags = o.flags(); o << " id: weight:" << '\n'; for (const auto&& el : v) { @@ -298,7 +298,7 @@ std::ostream& operator<<(std::ostream& o, const AssociationCollection -void to_json(nlohmann::json& j, const AssociationCollection& collection) { +void to_json(nlohmann::json& j, const LinkCollection& collection) { j = nlohmann::json::array(); for (auto&& elem : collection) { j.emplace_back(elem); @@ -308,9 +308,9 @@ void to_json(nlohmann::json& j, const AssociationCollection& collect namespace detail { template - podio::CollectionReadBuffers createAssociationBuffers(bool subsetColl) { + podio::CollectionReadBuffers createLinkBuffers(bool subsetColl) { auto readBuffers = podio::CollectionReadBuffers{}; - readBuffers.data = subsetColl ? nullptr : new AssociationDataContainer(); + readBuffers.data = subsetColl ? nullptr : new LinkDataContainer(); // Either it is a subset collection or we have two relations const auto nRefs = subsetColl ? 1 : 2; @@ -321,8 +321,8 @@ namespace detail { } readBuffers.createCollection = [](podio::CollectionReadBuffers buffers, bool isSubsetColl) { - AssociationCollectionData data(buffers, isSubsetColl); - return std::make_unique>(std::move(data), isSubsetColl); + LinkCollectionData data(buffers, isSubsetColl); + return std::make_unique>(std::move(data), isSubsetColl); }; readBuffers.recast = [](podio::CollectionReadBuffers& buffers) { @@ -336,7 +336,7 @@ namespace detail { // If we have data then we are not a subset collection and we have // to clean up all type erased buffers by casting them back to // something that we can delete - delete static_cast(buffers.data); + delete static_cast(buffers.data); } delete buffers.references; delete buffers.vectorMembers; @@ -346,15 +346,15 @@ namespace detail { } template - bool registerAssociationCollection(const std::string_view assocTypeName) { + bool registerLinkCollection(const std::string_view assocTypeName) { const static auto reg = [&assocTypeName]() { - const auto schemaVersion = AssociationCollection::schemaVersion; + const auto schemaVersion = LinkCollection::schemaVersion; auto& factory = CollectionBufferFactory::mutInstance(); - factory.registerCreationFunc(std::string(assocTypeName), schemaVersion, createAssociationBuffers); + factory.registerCreationFunc(std::string(assocTypeName), schemaVersion, createLinkBuffers); // For now passing the same schema version for from and current version - // simply to make SchemaEvolution aware of AssociationCollections + // simply to make SchemaEvolution aware of LinkCollections podio::SchemaEvolution::mutInstance().registerEvolutionFunc(std::string(assocTypeName), schemaVersion, schemaVersion, SchemaEvolution::noOpSchemaEvolution, SchemaEvolution::Priority::AutoGenerated); @@ -367,4 +367,4 @@ namespace detail { } // namespace podio -#endif // PODIO_DETAIL_ASSOCIATIONCOLLECTIONIMPL_H +#endif // PODIO_DETAIL_LINKCOLLECTIONIMPL_H diff --git a/include/podio/detail/LinkCollectionIterator.h b/include/podio/detail/LinkCollectionIterator.h new file mode 100644 index 000000000..99d9da49f --- /dev/null +++ b/include/podio/detail/LinkCollectionIterator.h @@ -0,0 +1,47 @@ +#ifndef PODIO_DETAIL_LINKCOLLECTIONITERATOR_H +#define PODIO_DETAIL_LINKCOLLECTIONITERATOR_H + +#include "podio/detail/LinkFwd.h" +#include "podio/utilities/MaybeSharedPtr.h" + +namespace podio { +template +class LinkCollectionIteratorT { + using AssocT = LinkT; + using LinkObjT = LinkObj; + +public: + LinkCollectionIteratorT(size_t index, const LinkObjPointerContainer* coll) : + m_index(index), m_object(podio::utils::MaybeSharedPtr{nullptr}), m_collection(coll) { + } + + LinkCollectionIteratorT(const LinkCollectionIteratorT&) = delete; + LinkCollectionIteratorT& operator=(const LinkCollectionIteratorT&) = delete; + + bool operator!=(const LinkCollectionIteratorT& other) const { + return m_index != other.m_index; // TODO: may not be complete + } + + AssocT operator*() { + m_object.m_obj = podio::utils::MaybeSharedPtr((*m_collection)[m_index]); + return m_object; + } + + AssocT* operator->() { + m_object.m_obj = podio::utils::MaybeSharedPtr((*m_collection)[m_index]); + return &m_object; + } + + LinkCollectionIteratorT& operator++() { + ++m_index; + return *this; + } + +private: + size_t m_index; + AssocT m_object; + const LinkObjPointerContainer* m_collection; +}; +} // namespace podio + +#endif // PODIO_DETAIL_LINKCOLLECTIONITERATOR_H diff --git a/include/podio/detail/LinkFwd.h b/include/podio/detail/LinkFwd.h new file mode 100644 index 000000000..492347230 --- /dev/null +++ b/include/podio/detail/LinkFwd.h @@ -0,0 +1,100 @@ +#ifndef PODIO_DETAIL_LINKFWD_H +#define PODIO_DETAIL_LINKFWD_H + +#include "podio/utilities/TypeHelpers.h" + +#include +#include +#include +#include +#include + +namespace podio { +namespace detail { + + /// Get the collection type name for an LinkCollection + /// + /// @tparam FromT the From type of the link + /// @tparam ToT the To type of the link + /// @returns a type string that is a valid c++ template instantiation + template + inline const std::string_view linkCollTypeName() { + const static std::string typeName = + std::string("podio::LinkCollection<") + FromT::typeName + "," + ToT::typeName + ">"; + return std::string_view{typeName}; + } + + /// Get the value type name for an LinkCollection + /// + /// @tparam FromT the From type of the link + /// @tparam ToT the To type of the link + /// @returns a type string that is a valid c++ template instantiation + template + inline const std::string_view linkTypeName() { + const static std::string typeName = std::string("podio::Link<") + FromT::typeName + "," + ToT::typeName + ">"; + return std::string_view{typeName}; + } + + /// Get an SIO friendly type name for an LinkCollection (necessary for + /// registration in the SIOBlockFactory) + /// + /// @tparam FromT the From type of the link + /// @tparam ToT the To type of + /// the link + /// @returns a string that uniquely identifies this combination + /// of From and To types + template + inline const std::string& linkSIOName() { + static auto n = std::string("LINK_FROM_") + FromT::typeName + "_TO_" + ToT::typeName; + std::replace(n.begin(), n.end(), ':', '_'); + return n; + } +} // namespace detail + +// Forward declarations and typedefs used throughout the whole Link +// business +template +class LinkObj; + +template +using LinkObjPointerContainer = std::deque*>; + +using LinkDataContainer = std::vector; + +template +class LinkT; + +template +using Link = LinkT, detail::GetDefaultHandleType, false>; + +template +using MutableLink = LinkT, detail::GetDefaultHandleType, true>; + +template +class LinkCollection; + +template +class LinkCollectionData; + +template +class LinkCollectionIteratorT; + +template +using LinkCollectionIterator = LinkCollectionIteratorT; + +template +using LinkMutableCollectionIterator = LinkCollectionIteratorT; + +} // namespace podio + +namespace std { +/// Specialization for enabling structure bindings for Links +template +struct tuple_size> : std::integral_constant {}; + +/// Specialization for enabling structure bindings for Links +template +struct tuple_element> : tuple_element> {}; +} // namespace std + +#endif // PODIO_DETAIL_LINKFWD_H diff --git a/include/podio/detail/AssociationObj.h b/include/podio/detail/LinkObj.h similarity index 50% rename from include/podio/detail/AssociationObj.h rename to include/podio/detail/LinkObj.h index 9f78b408f..3396840eb 100644 --- a/include/podio/detail/AssociationObj.h +++ b/include/podio/detail/LinkObj.h @@ -1,29 +1,29 @@ -#ifndef PODIO_DETAIL_ASSOCIATIONOBJ_H -#define PODIO_DETAIL_ASSOCIATIONOBJ_H +#ifndef PODIO_DETAIL_LINKOBJ_H +#define PODIO_DETAIL_LINKOBJ_H -#include "podio/detail/AssociationFwd.h" +#include "podio/detail/LinkFwd.h" #include "podio/ObjectID.h" namespace podio { template -class AssociationObj { +class LinkObj { - friend Association; - friend MutableAssociation; + friend Link; + friend MutableLink; public: /// Constructor - AssociationObj() : id(), weight(0.0f), m_from(nullptr), m_to(nullptr) { + LinkObj() : id(), weight(0.0f), m_from(nullptr), m_to(nullptr) { } /// Constructor from ObjectID and weight (does not initialize relations yet!) - AssociationObj(const podio::ObjectID id_, float weight_) : id(id_), weight(weight_) { + LinkObj(const podio::ObjectID id_, float weight_) : id(id_), weight(weight_) { } /// Copy constructor (deep-copy of relations) - AssociationObj(const AssociationObj& other) : id(), weight(other.weight), m_from(nullptr), m_to(nullptr) { + LinkObj(const LinkObj& other) : id(), weight(other.weight), m_from(nullptr), m_to(nullptr) { if (other.m_from) { m_from = new FromT(*other.m_from); } @@ -33,10 +33,10 @@ class AssociationObj { } /// No assignment operator - AssociationObj& operator=(const AssociationObj&) = delete; + LinkObj& operator=(const LinkObj&) = delete; /// Destructor - ~AssociationObj() { + ~LinkObj() { delete m_from; delete m_to; } @@ -50,4 +50,4 @@ class AssociationObj { } // namespace podio -#endif // PODIO_DETAIL_ASSOCIATIONOBJ_H +#endif // PODIO_DETAIL_LINKOBJ_H diff --git a/include/podio/detail/AssociationSIOBlock.h b/include/podio/detail/LinkSIOBlock.h similarity index 71% rename from include/podio/detail/AssociationSIOBlock.h rename to include/podio/detail/LinkSIOBlock.h index 4757da212..de4f7082b 100644 --- a/include/podio/detail/AssociationSIOBlock.h +++ b/include/podio/detail/LinkSIOBlock.h @@ -1,7 +1,7 @@ -#ifndef PODIO_DETAIL_ASSOCIATIONSIOBLOCK_H -#define PODIO_DETAIL_ASSOCIATIONSIOBLOCK_H +#ifndef PODIO_DETAIL_LINKSIOBLOCK_H +#define PODIO_DETAIL_LINKSIOBLOCK_H -#include "podio/detail/AssociationCollectionImpl.h" +#include "podio/detail/LinkCollectionImpl.h" #include "podio/CollectionBufferFactory.h" #include "podio/CollectionBuffers.h" @@ -15,24 +15,24 @@ namespace podio { template -class AssociationSIOBlock : public podio::SIOBlock { +class LinkSIOBlock : public podio::SIOBlock { public: - AssociationSIOBlock() : - SIOBlock(podio::detail::associationSIOName(), - sio::version::encode_version(AssociationCollection::schemaVersion, 0)) { + LinkSIOBlock() : + SIOBlock(podio::detail::linkSIOName(), + sio::version::encode_version(LinkCollection::schemaVersion, 0)) { podio::SIOBlockFactory::instance().registerBlockForCollection( - std::string(podio::detail::associationTypeName()), this); + std::string(podio::detail::linkTypeName()), this); } - AssociationSIOBlock(const std::string& name) : - SIOBlock(name, sio::version::encode_version(AssociationCollection::schemaVersion, 0)) { + LinkSIOBlock(const std::string& name) : + SIOBlock(name, sio::version::encode_version(LinkCollection::schemaVersion, 0)) { } void read(sio::read_device& device, sio::version_type version) override { auto& bufferFactory = podio::CollectionBufferFactory::instance(); // TODO: // - Error handling of empty optional - auto maybeBuffers = bufferFactory.createBuffers(std::string(podio::detail::associationCollTypeName()), + auto maybeBuffers = bufferFactory.createBuffers(std::string(podio::detail::linkCollTypeName()), sio::version::major_version(version), m_subsetColl); m_buffers = maybeBuffers.value(); @@ -72,10 +72,10 @@ class AssociationSIOBlock : public podio::SIOBlock { } SIOBlock* create(const std::string& name) const override { - return new AssociationSIOBlock(name); + return new LinkSIOBlock(name); } }; } // namespace podio -#endif // PODIO_DETAIL_ASSOCIATIONSIOBLOCK_H +#endif // PODIO_DETAIL_LINKSIOBLOCK_H diff --git a/python/podio/test_Frame.py b/python/podio/test_Frame.py index a093a0463..585cbc8d0 100644 --- a/python/podio/test_Frame.py +++ b/python/podio/test_Frame.py @@ -34,7 +34,7 @@ "WithNamespaceRelationCopy", "emptyCollection", "emptySubsetColl", - "associations", + "links", } # The expected collections from the extension (only present in the other_events category) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 69faeef3c..8e1d4b29f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -68,7 +68,7 @@ SET(core_headers ${PROJECT_SOURCE_DIR}/include/podio/DatamodelRegistry.h ${PROJECT_SOURCE_DIR}/include/podio/utilities/DatamodelRegistryIOHelpers.h ${PROJECT_SOURCE_DIR}/include/podio/GenericParameters.h - ${PROJECT_SOURCE_DIR}/include/podio/AssociationCollection.h + ${PROJECT_SOURCE_DIR}/include/podio/LinkCollection.h ) PODIO_ADD_LIB_AND_DICT(podio "${core_headers}" "${core_sources}" selection.xml) diff --git a/src/DatamodelRegistry.cc b/src/DatamodelRegistry.cc index 8cc4e7c7f..2a9bd572f 100644 --- a/src/DatamodelRegistry.cc +++ b/src/DatamodelRegistry.cc @@ -72,7 +72,7 @@ RelationNames DatamodelRegistry::getRelationNames(std::string_view typeName) con return {emptyVec, emptyVec}; } - if (typeName.substr(0, 18) == "podio::Association") { + if (typeName.substr(0, 11) == "podio::Link") { static constexpr auto fromName = "from"; static constexpr auto toName = "to"; const static std::vector relationNames = {fromName, toName}; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3e0516c14..74a1b57f8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -19,7 +19,7 @@ PODIO_GENERATE_DATAMODEL(datamodel datalayout.yaml headers sources # Use the cmake building blocks to add the different parts (conditionally) PODIO_ADD_DATAMODEL_CORE_LIB(TestDataModel "${headers}" "${sources}") -target_sources(TestDataModel PRIVATE DatamodelAssociations.cc) +target_sources(TestDataModel PRIVATE DatamodelLinks.cc) find_package(nlohmann_json 3.10) if (nlohmann_json_FOUND) message(STATUS "Found compatible version of JSON library, will add JSON support to test datamodel") @@ -29,7 +29,7 @@ endif() PODIO_ADD_ROOT_IO_DICT(TestDataModelDict TestDataModel "${headers}" src/selection.xml) PODIO_ADD_SIO_IO_BLOCKS(TestDataModel "${headers}" "${sources}") -target_sources(TestDataModelSioBlocks PRIVATE DatamodelAssociations.cc) +target_sources(TestDataModelSioBlocks PRIVATE DatamodelLinks.cc) # Build the extension data model and link it against the upstream model PODIO_GENERATE_DATAMODEL(extension_model datalayout_extension.yaml ext_headers ext_sources diff --git a/tests/DatamodelAssociations.cc b/tests/DatamodelAssociations.cc deleted file mode 100644 index b4dac4aca..000000000 --- a/tests/DatamodelAssociations.cc +++ /dev/null @@ -1,6 +0,0 @@ -#include "podio/AssociationCollection.h" - -#include "datamodel/ExampleClusterCollection.h" -#include "datamodel/ExampleHitCollection.h" - -PODIO_DECLARE_ASSOCIATION(ExampleHit, ExampleCluster) diff --git a/tests/DatamodelLinks.cc b/tests/DatamodelLinks.cc new file mode 100644 index 000000000..417e55859 --- /dev/null +++ b/tests/DatamodelLinks.cc @@ -0,0 +1,6 @@ +#include "podio/LinkCollection.h" + +#include "datamodel/ExampleClusterCollection.h" +#include "datamodel/ExampleHitCollection.h" + +PODIO_DECLARE_LINK(ExampleHit, ExampleCluster) diff --git a/tests/frame_test_common.h b/tests/frame_test_common.h index f4df71257..292cde3d8 100644 --- a/tests/frame_test_common.h +++ b/tests/frame_test_common.h @@ -24,6 +24,6 @@ static const std::vector collsToWrite = {"mcparticles", "WithNamespaceRelationCopy", "emptyCollection", "emptySubsetColl", - "associations"}; + "links"}; #endif // PODIO_TESTS_FRAME_TEST_COMMON_H diff --git a/tests/read_test.h b/tests/read_test.h index 68032a198..9c6586298 100644 --- a/tests/read_test.h +++ b/tests/read_test.h @@ -14,8 +14,8 @@ #include "datamodel/ExampleWithVectorMemberCollection.h" // podio specific includes -#include "podio/AssociationCollection.h" #include "podio/Frame.h" +#include "podio/LinkCollection.h" #include "podio/UserDataCollection.h" #include "podio/podioVersion.h" @@ -29,8 +29,8 @@ #include #include -// Define an association that is used for the I/O tests -using TestAssocCollection = podio::AssociationCollection; +// Define an link that is used for the I/O tests +using TestLinkCollection = podio::LinkCollection; template bool check_fixed_width_value(FixedWidthT actual, FixedWidthT expected, const std::string& type) { @@ -417,20 +417,20 @@ void processEvent(const podio::Frame& event, int eventNum, podio::version::Versi } } - // ======================= Associations ========================== + // ======================= Links ========================== if (fileVersion >= podio::version::Version{1, 0, 99}) { - auto& associations = event.get("associations"); - const auto nAssocs = std::min(clusters.size(), hits.size()); - if (associations.size() != nAssocs) { - throw std::runtime_error("AssociationsCollection does not have the expected size"); - } - int assocIndex = 0; - for (auto assoc : associations) { - if (!((assoc.getWeight() == 0.5 * assocIndex) && (assoc.getFrom() == hits[assocIndex]) && - (assoc.getTo() == clusters[nAssocs - 1 - assocIndex]))) { - throw std::runtime_error("Association does not have expected content"); + auto& links = event.get("links"); + const auto nLinks = std::min(clusters.size(), hits.size()); + if (links.size() != nLinks) { + throw std::runtime_error("LinksCollection does not have the expected size"); + } + int linkIndex = 0; + for (auto link : links) { + if (!((link.getWeight() == 0.5 * linkIndex) && (link.getFrom() == hits[linkIndex]) && + (link.getTo() == clusters[nLinks - 1 - linkIndex]))) { + throw std::runtime_error("Link does not have expected content"); } - assocIndex++; + linkIndex++; } } } diff --git a/tests/unittests/CMakeLists.txt b/tests/unittests/CMakeLists.txt index 144b97115..926b0fe8c 100644 --- a/tests/unittests/CMakeLists.txt +++ b/tests/unittests/CMakeLists.txt @@ -40,7 +40,7 @@ if(NOT Catch2_FOUND) endif() find_package(Threads REQUIRED) -add_executable(unittest_podio unittest.cpp frame.cpp buffer_factory.cpp interface_types.cpp std_interoperability.cpp associations.cpp) +add_executable(unittest_podio unittest.cpp frame.cpp buffer_factory.cpp interface_types.cpp std_interoperability.cpp links.cpp) target_link_libraries(unittest_podio PUBLIC TestDataModel PRIVATE Catch2::Catch2WithMain Threads::Threads podio::podioRootIO) if (ENABLE_SIO) target_link_libraries(unittest_podio PRIVATE podio::podioSioIO) diff --git a/tests/unittests/associations.cpp b/tests/unittests/associations.cpp deleted file mode 100644 index 7e6d87167..000000000 --- a/tests/unittests/associations.cpp +++ /dev/null @@ -1,412 +0,0 @@ -#include "catch2/catch_test_macros.hpp" - -#include "podio/AssociationCollection.h" - -#include "datamodel/ExampleClusterCollection.h" -#include "datamodel/ExampleHitCollection.h" - -#include - -// Test datatypes (spelling them out here explicitly to make sure that -// assumptions about typedefs actually hold) -using TestA = podio::Association; -using TestMutA = podio::MutableAssociation; -using TestAColl = podio::AssociationCollection; -using TestAIter = podio::AssociationCollectionIterator; -using TestAMutIter = podio::AssociationMutableCollectionIterator; - -TEST_CASE("Association constness", "[associations][static-checks]") { - STATIC_REQUIRE(std::is_same_v().getFrom()), const ExampleHit>); - STATIC_REQUIRE(std::is_same_v().getTo()), const ExampleCluster>); - - STATIC_REQUIRE(std::is_same_v().getFrom()), const ExampleHit>); - STATIC_REQUIRE(std::is_same_v().getTo()), const ExampleCluster>); -} - -TEST_CASE("Association basics", "[associations]") { - // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks): There are quite a few - // false positives here from clang-tidy that we are confident are false - // positives, because we don't see issues in our builds with sanitizers - auto cluster = MutableExampleCluster(); - auto hit = MutableExampleHit(); - - auto mutAssoc = TestMutA(); - mutAssoc.setWeight(3.14f); - mutAssoc.setFrom(hit); - mutAssoc.setTo(cluster); - - REQUIRE(mutAssoc.getWeight() == 3.14f); - REQUIRE(mutAssoc.getFrom() == hit); - REQUIRE(mutAssoc.getTo() == cluster); - - SECTION("Copying") { - auto otherAssoc = mutAssoc; - REQUIRE(otherAssoc.getWeight() == 3.14f); - REQUIRE(otherAssoc.getFrom() == hit); - REQUIRE(otherAssoc.getTo() == cluster); - - auto otherCluster = ExampleCluster(); - auto otherHit = ExampleHit(); - otherAssoc.setFrom(otherHit); - otherAssoc.setTo(otherCluster); - otherAssoc.setWeight(42.0f); - REQUIRE(otherAssoc.getWeight() == 42.0f); - REQUIRE(otherAssoc.getFrom() == otherHit); - REQUIRE(otherAssoc.getTo() == otherCluster); - - // Make sure original association changes as well - REQUIRE(mutAssoc.getWeight() == 42.0f); - REQUIRE(mutAssoc.getFrom() == otherHit); - REQUIRE(mutAssoc.getTo() == otherCluster); - } - - SECTION("Assignment") { - auto otherAssoc = TestMutA(); - otherAssoc = mutAssoc; - REQUIRE(otherAssoc.getWeight() == 3.14f); - REQUIRE(otherAssoc.getFrom() == hit); - REQUIRE(otherAssoc.getTo() == cluster); - - auto otherCluster = ExampleCluster(); - auto otherHit = ExampleHit(); - otherAssoc.setFrom(otherHit); - otherAssoc.setTo(otherCluster); - otherAssoc.setWeight(42.0f); - REQUIRE(otherAssoc.getWeight() == 42.0f); - REQUIRE(otherAssoc.getFrom() == otherHit); - REQUIRE(otherAssoc.getTo() == otherCluster); - - // Make sure original association changes as well - REQUIRE(mutAssoc.getWeight() == 42.0f); - REQUIRE(mutAssoc.getFrom() == otherHit); - REQUIRE(mutAssoc.getTo() == otherCluster); - } - - SECTION("Implicit conversion") { - // Use an immediately invoked lambda to check that the implicit conversion - // is working as desired - [hit, cluster](TestA assoc) { // NOLINT(performance-unnecessary-value-param): - // We want the value here to force teh - // conversion - REQUIRE(assoc.getWeight() == 3.14f); - REQUIRE(assoc.getFrom() == hit); - REQUIRE(assoc.getTo() == cluster); - }(mutAssoc); - } - - SECTION("Cloning") { - auto otherAssoc = mutAssoc.clone(); - REQUIRE(otherAssoc.getWeight() == 3.14f); - REQUIRE(otherAssoc.getFrom() == hit); - REQUIRE(otherAssoc.getTo() == cluster); - - auto otherCluster = ExampleCluster(); - auto otherHit = ExampleHit(); - otherAssoc.setFrom(otherHit); - otherAssoc.setTo(otherCluster); - otherAssoc.setWeight(42.0f); - REQUIRE(otherAssoc.getWeight() == 42.0f); - REQUIRE(otherAssoc.getFrom() == otherHit); - REQUIRE(otherAssoc.getTo() == otherCluster); - - // Make sure original association is unchanged - REQUIRE(mutAssoc.getWeight() == 3.14f); - REQUIRE(mutAssoc.getFrom() == hit); - REQUIRE(mutAssoc.getTo() == cluster); - - // Check cloning from an immutable one - TestA assoc = mutAssoc; - auto anotherAssoc = assoc.clone(); - anotherAssoc.setFrom(otherHit); - anotherAssoc.setTo(otherCluster); - anotherAssoc.setWeight(42.0f); - REQUIRE(anotherAssoc.getWeight() == 42.0f); - REQUIRE(anotherAssoc.getFrom() == otherHit); - REQUIRE(anotherAssoc.getTo() == otherCluster); - - // Cloning without relations - auto assocNoRel = assoc.clone(false); - REQUIRE_FALSE(assocNoRel.getFrom().isAvailable()); - REQUIRE_FALSE(assocNoRel.getTo().isAvailable()); - REQUIRE(assocNoRel.getWeight() == 3.14f); - } - - SECTION("Equality operator") { - auto otherAssoc = mutAssoc; // NOLINT(performance-unnecessary-copy-initialization) - REQUIRE(otherAssoc == mutAssoc); - - // Mutable and immutable associations should be comparable - TestA assoc = mutAssoc; - REQUIRE(assoc == mutAssoc); - - // operator!= is also defined and working - auto newAssoc = TestA{}; - REQUIRE(otherAssoc != newAssoc); - REQUIRE(assoc != newAssoc); - } -} -// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) - -TEST_CASE("Associations templated accessors", "[associations]") { - // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks): There are quite a few - // false positives here from clang-tidy that we are confident are false - // positives, because we don't see issues in our builds with sanitizers - ExampleHit hit; - ExampleCluster cluster; - - TestMutA assoc; - assoc.set(hit); - assoc.set(cluster); - assoc.setWeight(1.0); - - SECTION("Mutable Association") { - REQUIRE(hit == assoc.get()); - REQUIRE(cluster == assoc.get()); - - const auto& [h, c, w] = assoc; - REQUIRE(h == hit); - REQUIRE(c == cluster); - REQUIRE(w == 1.0); - } - - SECTION("Immutable association") { - TestA a{assoc}; - - REQUIRE(hit == a.get()); - REQUIRE(cluster == a.get()); - - const auto& [h, c, w] = a; - REQUIRE(h == hit); - REQUIRE(c == cluster); - REQUIRE(w == 1.0); - } -} -// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) - -TEST_CASE("AssociationCollection constness", "[associations][static-checks][const-correctness]") { - // Test type-aliases in AssociationCollection - STATIC_REQUIRE(std::is_same_v); - STATIC_REQUIRE(std::is_same_v); - - SECTION("const collections with const iterators") { - const auto coll = TestAColl(); - // this essentially checks the whole "chain" from begin() / end() through - // iterator operators - for (auto assoc : coll) { - STATIC_REQUIRE(std::is_same_v); // const collection iterators should only return - // immutable objects - } - - // check the individual steps again from above, to see where things fail if they fail - STATIC_REQUIRE(std::is_same_v().begin()), - TestAColl::const_iterator>); // const collectionb begin() should return a - // AssociationCollectionIterator - - STATIC_REQUIRE(std::is_same_v().end()), - TestAColl::const_iterator>); // const collectionb end() should return a - // AssociationCollectionIterator - - STATIC_REQUIRE(std::is_same_v().begin()), - TestA>); // AssociationCollectionIterator should only give access to immutable - // objects - - STATIC_REQUIRE(std::is_same_v().operator->()), - TestA*>); // AssociationCollectionIterator should only give access to immutable - // objects - } - - SECTION("non-const collections with mutable iterators") { - auto coll = TestAColl(); - // this essentially checks the whole "chain" from begin() / end() through - // iterator operators - for (auto assoc : coll) { - STATIC_REQUIRE(std::is_same_v); // collection iterators should return return - // mutable objects - } - - // check the individual steps again from above, to see where things fail if they fail - STATIC_REQUIRE(std::is_same_v().begin()), - TestAColl::iterator>); // collection begin() should return a - // MutableCollectionIterator - - STATIC_REQUIRE(std::is_same_v().end()), - TestAColl::iterator>); // collectionb end() should return a - // MutableCollectionIterator - - STATIC_REQUIRE(std::is_same_v().begin()), - TestMutA>); // MutableCollectionIterator should give access to immutable - // mutable objects - - STATIC_REQUIRE(std::is_same_v().operator->()), - TestMutA*>); // MutableCollectionIterator should give access to immutable - // mutable objects - } - - SECTION("const correct indexed access to const collections") { - STATIC_REQUIRE(std::is_same_v()[0]), - TestA>); // const collections should only have indexed indexed access to immutable - // objects - - STATIC_REQUIRE(std::is_same_v().at(0)), - TestA>); // const collections should only have indexed indexed access to immutable - // objects - } - - SECTION("const correct indexed access to collections") { - STATIC_REQUIRE(std::is_same_v()[0]), - TestMutA>); // collections should have indexed indexed access to mutable objects - - STATIC_REQUIRE(std::is_same_v().at(0)), - TestMutA>); // collections should have indexed indexed access to mutable objects - } -} - -TEST_CASE("AssociationCollection subset collection", "[associations][subset-colls]") { - // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks): There are quite a few - // false positives here from clang-tidy that we are confident are false - // positives, because we don't see issues in our builds with sanitizers - auto assocs = TestAColl(); - auto assoc1 = assocs.create(); - assoc1.setWeight(1.0f); - auto assoc2 = assocs.create(); - assoc2.setWeight(2.0f); - - auto assocRefs = TestAColl(); - assocRefs.setSubsetCollection(); - for (const auto a : assocs) { - assocRefs.push_back(a); - } - - SECTION("Collection iterators work with subset collections") { - - // index-based looping / access - for (size_t i = 0; i < assocRefs.size(); ++i) { - REQUIRE(assocRefs[i].getWeight() == i + 1); - } - - // range-based for loop - int index = 1; - for (const auto a : assocRefs) { - REQUIRE(a.getWeight() == index++); - } - } - - SECTION("Conversion failures") { - // Cannot convert into a subset collection if elements already present - REQUIRE_THROWS_AS(assocs.setSubsetCollection(), std::logic_error); - - // Connot convert a subset collection into a normal collection - REQUIRE_THROWS_AS(assocRefs.setSubsetCollection(false), std::logic_error); - } - - SECTION("Subset collection only handles tracked objects") { - auto assoc = TestA(); - REQUIRE_THROWS_AS(assocRefs.push_back(assoc), std::invalid_argument); - REQUIRE_THROWS_AS(assocRefs.create(), std::logic_error); - } -} -// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) - -auto createAssocCollections(const size_t nElements = 3u) { - auto colls = std::make_tuple(TestAColl(), ExampleHitCollection(), ExampleClusterCollection()); - - auto& [assocColl, hitColl, clusterColl] = colls; - for (auto i = 0u; i < nElements; ++i) { - auto hit = hitColl.create(); - auto cluster = clusterColl.create(); - } - - for (auto i = 0u; i < nElements; ++i) { - auto assoc = assocColl.create(); - assoc.setWeight(i); - // Fill the relations in opposite orders to at least uncover issues that - // could be hidden by running the indices in parallel - assoc.setFrom(hitColl[i]); - assoc.setTo(clusterColl[nElements - i - 1]); - } - - return colls; -} - -void checkCollections(const TestAColl& assocs, const ExampleHitCollection& hits, - const ExampleClusterCollection& clusters, const size_t nElements = 3u) { - REQUIRE(assocs.size() == 3); - REQUIRE(hits.size() == 3); - REQUIRE(clusters.size() == 3); - - size_t index = 0; - for (auto assoc : assocs) { - REQUIRE(assoc.getWeight() == index); - REQUIRE(assoc.getFrom() == hits[index]); - REQUIRE(assoc.getTo() == clusters[nElements - index - 1]); - - index++; - } -} - -TEST_CASE("AssociationCollection looping", "[associations][basics]") { - const auto [assocColl, hitColl, clusterColl] = createAssocCollections(); - - int i = 0; - const auto collSize = assocColl.size(); - for (const auto& [from, to, weight] : assocColl) { - STATIC_REQUIRE(std::is_same_v); - STATIC_REQUIRE(std::is_same_v); - STATIC_REQUIRE(std::is_same_v); - - REQUIRE(from == hitColl[i]); - REQUIRE(to == clusterColl[collSize - 1 - i]); - REQUIRE(weight == i); - i++; - } -} - -TEST_CASE("AssociationCollection movability", "[associations][move-semantics][collections]") { - // Setup a few collections for testing - auto [assocColl, hitColl, clusterColl] = createAssocCollections(); - - // Check that after the setup everything is as expected - checkCollections(assocColl, hitColl, clusterColl); - - SECTION("Move constructor and assignment") { - // NOTE: moving datatype collections is already covered by respective tests - auto newAssocs = std::move(assocColl); - checkCollections(newAssocs, hitColl, clusterColl); - - auto newerAssocs = TestAColl(); - newerAssocs = std::move(newAssocs); - checkCollections(newerAssocs, hitColl, clusterColl); - } - - SECTION("Prepared collections can be move assigned/constructed") { - assocColl.prepareForWrite(); - auto newAssocs = std::move(assocColl); - // checkCollections(newAssocs, hitColl, clusterColl); - - newAssocs.prepareForWrite(); - auto newerAssocs = TestAColl(); - newerAssocs = std::move(newAssocs); - // checkCollections(newAssocs, hitColl, clusterColl); - } - - SECTION("Subset collections can be moved") { - // Create a subset collection to move from - auto subsetAssocs = TestAColl(); - subsetAssocs.setSubsetCollection(); - for (auto a : assocColl) { - subsetAssocs.push_back(a); - } - checkCollections(subsetAssocs, hitColl, clusterColl); - - // Move constructor - auto newSubsetAssocs = std::move(subsetAssocs); - checkCollections(newSubsetAssocs, hitColl, clusterColl); - REQUIRE(newSubsetAssocs.isSubsetCollection()); - - // Move assignment - auto evenNewerAssocs = TestAColl(); - evenNewerAssocs = std::move(newSubsetAssocs); - checkCollections(evenNewerAssocs, hitColl, clusterColl); - REQUIRE(evenNewerAssocs.isSubsetCollection()); - } -} diff --git a/tests/unittests/links.cpp b/tests/unittests/links.cpp new file mode 100644 index 000000000..f3a4112b1 --- /dev/null +++ b/tests/unittests/links.cpp @@ -0,0 +1,412 @@ +#include "catch2/catch_test_macros.hpp" + +#include "podio/LinkCollection.h" + +#include "datamodel/ExampleClusterCollection.h" +#include "datamodel/ExampleHitCollection.h" + +#include + +// Test datatypes (spelling them out here explicitly to make sure that +// assumptions about typedefs actually hold) +using TestL = podio::Link; +using TestMutL = podio::MutableLink; +using TestLColl = podio::LinkCollection; +using TestLIter = podio::LinkCollectionIterator; +using TestLMutIter = podio::LinkMutableCollectionIterator; + +TEST_CASE("Link constness", "[links][static-checks]") { + STATIC_REQUIRE(std::is_same_v().getFrom()), const ExampleHit>); + STATIC_REQUIRE(std::is_same_v().getTo()), const ExampleCluster>); + + STATIC_REQUIRE(std::is_same_v().getFrom()), const ExampleHit>); + STATIC_REQUIRE(std::is_same_v().getTo()), const ExampleCluster>); +} + +TEST_CASE("Link basics", "[links]") { + // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks): There are quite a few + // false positives here from clang-tidy that we are confident are false + // positives, because we don't see issues in our builds with sanitizers + auto cluster = MutableExampleCluster(); + auto hit = MutableExampleHit(); + + auto mutLink = TestMutL(); + mutLink.setWeight(3.14f); + mutLink.setFrom(hit); + mutLink.setTo(cluster); + + REQUIRE(mutLink.getWeight() == 3.14f); + REQUIRE(mutLink.getFrom() == hit); + REQUIRE(mutLink.getTo() == cluster); + + SECTION("Copying") { + auto otherLink = mutLink; + REQUIRE(otherLink.getWeight() == 3.14f); + REQUIRE(otherLink.getFrom() == hit); + REQUIRE(otherLink.getTo() == cluster); + + auto otherCluster = ExampleCluster(); + auto otherHit = ExampleHit(); + otherLink.setFrom(otherHit); + otherLink.setTo(otherCluster); + otherLink.setWeight(42.0f); + REQUIRE(otherLink.getWeight() == 42.0f); + REQUIRE(otherLink.getFrom() == otherHit); + REQUIRE(otherLink.getTo() == otherCluster); + + // Make sure original link changes as well + REQUIRE(mutLink.getWeight() == 42.0f); + REQUIRE(mutLink.getFrom() == otherHit); + REQUIRE(mutLink.getTo() == otherCluster); + } + + SECTION("Assignment") { + auto otherLink = TestMutL(); + otherLink = mutLink; + REQUIRE(otherLink.getWeight() == 3.14f); + REQUIRE(otherLink.getFrom() == hit); + REQUIRE(otherLink.getTo() == cluster); + + auto otherCluster = ExampleCluster(); + auto otherHit = ExampleHit(); + otherLink.setFrom(otherHit); + otherLink.setTo(otherCluster); + otherLink.setWeight(42.0f); + REQUIRE(otherLink.getWeight() == 42.0f); + REQUIRE(otherLink.getFrom() == otherHit); + REQUIRE(otherLink.getTo() == otherCluster); + + // Make sure original link changes as well + REQUIRE(mutLink.getWeight() == 42.0f); + REQUIRE(mutLink.getFrom() == otherHit); + REQUIRE(mutLink.getTo() == otherCluster); + } + + SECTION("Implicit conversion") { + // Use an immediately invoked lambda to check that the implicit conversion + // is working as desired + [hit, cluster](TestL link) { // NOLINT(performance-unnecessary-value-param): + // We want the value here to force teh + // conversion + REQUIRE(link.getWeight() == 3.14f); + REQUIRE(link.getFrom() == hit); + REQUIRE(link.getTo() == cluster); + }(mutLink); + } + + SECTION("Cloning") { + auto otherLink = mutLink.clone(); + REQUIRE(otherLink.getWeight() == 3.14f); + REQUIRE(otherLink.getFrom() == hit); + REQUIRE(otherLink.getTo() == cluster); + + auto otherCluster = ExampleCluster(); + auto otherHit = ExampleHit(); + otherLink.setFrom(otherHit); + otherLink.setTo(otherCluster); + otherLink.setWeight(42.0f); + REQUIRE(otherLink.getWeight() == 42.0f); + REQUIRE(otherLink.getFrom() == otherHit); + REQUIRE(otherLink.getTo() == otherCluster); + + // Make sure original link is unchanged + REQUIRE(mutLink.getWeight() == 3.14f); + REQUIRE(mutLink.getFrom() == hit); + REQUIRE(mutLink.getTo() == cluster); + + // Check cloning from an immutable one + TestL link = mutLink; + auto anotherLink = link.clone(); + anotherLink.setFrom(otherHit); + anotherLink.setTo(otherCluster); + anotherLink.setWeight(42.0f); + REQUIRE(anotherLink.getWeight() == 42.0f); + REQUIRE(anotherLink.getFrom() == otherHit); + REQUIRE(anotherLink.getTo() == otherCluster); + + // Cloning without relations + auto linkNoRel = link.clone(false); + REQUIRE_FALSE(linkNoRel.getFrom().isAvailable()); + REQUIRE_FALSE(linkNoRel.getTo().isAvailable()); + REQUIRE(linkNoRel.getWeight() == 3.14f); + } + + SECTION("Equality operator") { + auto otherLink = mutLink; // NOLINT(performance-unnecessary-copy-initialization) + REQUIRE(otherLink == mutLink); + + // Mutable and immutable links should be comparable + TestL link = mutLink; + REQUIRE(link == mutLink); + + // operator!= is also defined and working + auto newLink = TestL{}; + REQUIRE(otherLink != newLink); + REQUIRE(link != newLink); + } +} +// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) + +TEST_CASE("Links templated accessors", "[links]") { + // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks): There are quite a few + // false positives here from clang-tidy that we are confident are false + // positives, because we don't see issues in our builds with sanitizers + ExampleHit hit; + ExampleCluster cluster; + + TestMutL link; + link.set(hit); + link.set(cluster); + link.setWeight(1.0); + + SECTION("Mutable Link") { + REQUIRE(hit == link.get()); + REQUIRE(cluster == link.get()); + + const auto& [h, c, w] = link; + REQUIRE(h == hit); + REQUIRE(c == cluster); + REQUIRE(w == 1.0); + } + + SECTION("Immutable link") { + TestL a{link}; + + REQUIRE(hit == a.get()); + REQUIRE(cluster == a.get()); + + const auto& [h, c, w] = a; + REQUIRE(h == hit); + REQUIRE(c == cluster); + REQUIRE(w == 1.0); + } +} +// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) + +TEST_CASE("LinkCollection constness", "[links][static-checks][const-correctness]") { + // Test type-aliases in LinkCollection + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + + SECTION("const collections with const iterators") { + const auto coll = TestLColl(); + // this essentially checks the whole "chain" from begin() / end() through + // iterator operators + for (auto link : coll) { + STATIC_REQUIRE(std::is_same_v); // const collection iterators should only return + // immutable objects + } + + // check the individual steps again from above, to see where things fail if they fail + STATIC_REQUIRE(std::is_same_v().begin()), + TestLColl::const_iterator>); // const collectionb begin() should return a + // LinkCollectionIterator + + STATIC_REQUIRE(std::is_same_v().end()), + TestLColl::const_iterator>); // const collectionb end() should return a + // LinkCollectionIterator + + STATIC_REQUIRE(std::is_same_v().begin()), + TestL>); // LinkCollectionIterator should only give access to immutable + // objects + + STATIC_REQUIRE(std::is_same_v().operator->()), + TestL*>); // LinkCollectionIterator should only give access to immutable + // objects + } + + SECTION("non-const collections with mutable iterators") { + auto coll = TestLColl(); + // this essentially checks the whole "chain" from begin() / end() through + // iterator operators + for (auto link : coll) { + STATIC_REQUIRE(std::is_same_v); // collection iterators should return return + // mutable objects + } + + // check the individual steps again from above, to see where things fail if they fail + STATIC_REQUIRE(std::is_same_v().begin()), + TestLColl::iterator>); // collection begin() should return a + // MutableCollectionIterator + + STATIC_REQUIRE(std::is_same_v().end()), + TestLColl::iterator>); // collectionb end() should return a + // MutableCollectionIterator + + STATIC_REQUIRE(std::is_same_v().begin()), + TestMutL>); // MutableCollectionIterator should give access to immutable + // mutable objects + + STATIC_REQUIRE(std::is_same_v().operator->()), + TestMutL*>); // MutableCollectionIterator should give access to immutable + // mutable objects + } + + SECTION("const correct indexed access to const collections") { + STATIC_REQUIRE(std::is_same_v()[0]), + TestL>); // const collections should only have indexed indexed access to immutable + // objects + + STATIC_REQUIRE(std::is_same_v().at(0)), + TestL>); // const collections should only have indexed indexed access to immutable + // objects + } + + SECTION("const correct indexed access to collections") { + STATIC_REQUIRE(std::is_same_v()[0]), + TestMutL>); // collections should have indexed indexed access to mutable objects + + STATIC_REQUIRE(std::is_same_v().at(0)), + TestMutL>); // collections should have indexed indexed access to mutable objects + } +} + +TEST_CASE("LinkCollection subset collection", "[links][subset-colls]") { + // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks): There are quite a few + // false positives here from clang-tidy that we are confident are false + // positives, because we don't see issues in our builds with sanitizers + auto links = TestLColl(); + auto link1 = links.create(); + link1.setWeight(1.0f); + auto link2 = links.create(); + link2.setWeight(2.0f); + + auto linkRefs = TestLColl(); + linkRefs.setSubsetCollection(); + for (const auto a : links) { + linkRefs.push_back(a); + } + + SECTION("Collection iterators work with subset collections") { + + // index-based looping / access + for (size_t i = 0; i < linkRefs.size(); ++i) { + REQUIRE(linkRefs[i].getWeight() == i + 1); + } + + // range-based for loop + int index = 1; + for (const auto a : linkRefs) { + REQUIRE(a.getWeight() == index++); + } + } + + SECTION("Conversion failures") { + // Cannot convert into a subset collection if elements already present + REQUIRE_THROWS_AS(links.setSubsetCollection(), std::logic_error); + + // Connot convert a subset collection into a normal collection + REQUIRE_THROWS_AS(linkRefs.setSubsetCollection(false), std::logic_error); + } + + SECTION("Subset collection only handles tracked objects") { + auto link = TestL(); + REQUIRE_THROWS_AS(linkRefs.push_back(link), std::invalid_argument); + REQUIRE_THROWS_AS(linkRefs.create(), std::logic_error); + } +} +// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) + +auto createLinkCollections(const size_t nElements = 3u) { + auto colls = std::make_tuple(TestLColl(), ExampleHitCollection(), ExampleClusterCollection()); + + auto& [linkColl, hitColl, clusterColl] = colls; + for (auto i = 0u; i < nElements; ++i) { + auto hit = hitColl.create(); + auto cluster = clusterColl.create(); + } + + for (auto i = 0u; i < nElements; ++i) { + auto link = linkColl.create(); + link.setWeight(i); + // Fill the relations in opposite orders to at least uncover issues that + // could be hidden by running the indices in parallel + link.setFrom(hitColl[i]); + link.setTo(clusterColl[nElements - i - 1]); + } + + return colls; +} + +void checkCollections(const TestLColl& links, const ExampleHitCollection& hits, + const ExampleClusterCollection& clusters, const size_t nElements = 3u) { + REQUIRE(links.size() == 3); + REQUIRE(hits.size() == 3); + REQUIRE(clusters.size() == 3); + + size_t index = 0; + for (auto link : links) { + REQUIRE(link.getWeight() == index); + REQUIRE(link.getFrom() == hits[index]); + REQUIRE(link.getTo() == clusters[nElements - index - 1]); + + index++; + } +} + +TEST_CASE("LinkCollection looping", "[links][basics]") { + const auto [linkColl, hitColl, clusterColl] = createLinkCollections(); + + int i = 0; + const auto collSize = linkColl.size(); + for (const auto& [from, to, weight] : linkColl) { + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + + REQUIRE(from == hitColl[i]); + REQUIRE(to == clusterColl[collSize - 1 - i]); + REQUIRE(weight == i); + i++; + } +} + +TEST_CASE("LinkCollection movability", "[links][move-semantics][collections]") { + // Setup a few collections for testing + auto [linkColl, hitColl, clusterColl] = createLinkCollections(); + + // Check that after the setup everything is as expected + checkCollections(linkColl, hitColl, clusterColl); + + SECTION("Move constructor and assignment") { + // NOTE: moving datatype collections is already covered by respective tests + auto newLinks = std::move(linkColl); + checkCollections(newLinks, hitColl, clusterColl); + + auto newerLinks = TestLColl(); + newerLinks = std::move(newLinks); + checkCollections(newerLinks, hitColl, clusterColl); + } + + SECTION("Prepared collections can be move assigned/constructed") { + linkColl.prepareForWrite(); + auto newLinks = std::move(linkColl); + // checkCollections(newLinks, hitColl, clusterColl); + + newLinks.prepareForWrite(); + auto newerLinks = TestLColl(); + newerLinks = std::move(newLinks); + // checkCollections(newLinks, hitColl, clusterColl); + } + + SECTION("Subset collections can be moved") { + // Create a subset collection to move from + auto subsetLinks = TestLColl(); + subsetLinks.setSubsetCollection(); + for (auto a : linkColl) { + subsetLinks.push_back(a); + } + checkCollections(subsetLinks, hitColl, clusterColl); + + // Move constructor + auto newSubsetLinks = std::move(subsetLinks); + checkCollections(newSubsetLinks, hitColl, clusterColl); + REQUIRE(newSubsetLinks.isSubsetCollection()); + + // Move assignment + auto evenNewerLinks = TestLColl(); + evenNewerLinks = std::move(newSubsetLinks); + checkCollections(evenNewerLinks, hitColl, clusterColl); + REQUIRE(evenNewerLinks.isSubsetCollection()); + } +} diff --git a/tests/write_frame.h b/tests/write_frame.h index 004bb9a86..baa7bf99e 100644 --- a/tests/write_frame.h +++ b/tests/write_frame.h @@ -20,15 +20,15 @@ #include "extension_model/ExternalComponentTypeCollection.h" #include "extension_model/ExternalRelationTypeCollection.h" -#include "podio/AssociationCollection.h" #include "podio/Frame.h" +#include "podio/LinkCollection.h" #include "podio/UserDataCollection.h" #include #include -// Define an association that is used for the I/O tests -using TestAssocCollection = podio::AssociationCollection; +// Define an link that is used for the I/O tests +using TestLinkCollection = podio::LinkCollection; auto createMCCollection() { auto mcps = ExampleMCCollection(); @@ -384,20 +384,20 @@ auto createExampleWithInterfaceCollection(const ExampleHitCollection& hits, cons return coll; } -auto createAssociationCollection(const ExampleHitCollection& hits, const ExampleClusterCollection& clusters) { - TestAssocCollection associations{}; - const auto nAssocs = std::min(clusters.size(), hits.size()); - for (size_t iA = 0; iA < nAssocs; ++iA) { - auto assoc = associations.create(); - assoc.setWeight(0.5 * iA); +auto createLinkCollection(const ExampleHitCollection& hits, const ExampleClusterCollection& clusters) { + TestLinkCollection links{}; + const auto nLinks = std::min(clusters.size(), hits.size()); + for (size_t iA = 0; iA < nLinks; ++iA) { + auto link = links.create(); + link.setWeight(0.5 * iA); // Fill in opposite "order" to at least make sure that we uncover issues // that would otherwise be masked by parallel running of indices - assoc.setFrom(hits[iA]); - assoc.setTo(clusters[nAssocs - 1 - iA]); + link.setFrom(hits[iA]); + link.setTo(clusters[nLinks - 1 - iA]); } - return associations; + return links; } podio::Frame makeFrame(int iFrame) { @@ -460,7 +460,7 @@ podio::Frame makeFrame(int iFrame) { frame.put(createExtensionExternalRelationCollection(iFrame, hits, clusters), "extension_ExternalRelation"); frame.put(createExampleWithInterfaceCollection(hits, clusters, mcps), "interface_examples"); - frame.put(createAssociationCollection(hits, clusters), "associations"); + frame.put(createLinkCollection(hits, clusters), "links"); return frame; }