diff --git a/icarusalg/Utilities/AssnsCrosser.h b/icarusalg/Utilities/AssnsCrosser.h deleted file mode 100644 index bb793bb..0000000 --- a/icarusalg/Utilities/AssnsCrosser.h +++ /dev/null @@ -1,1828 +0,0 @@ -/** - * @file icarusalg/Utilities/AssnsCrosser.h - * @brief Unit test for `icarus::ns::util::AssnsCrosser` class and utilities. - * @author Gianluca Petrillo (petrillo@slac.stanford.edu) - * @date June 9, 2023 - */ - -#ifndef ICARUSALG_UTILITIES_ASSNSCROSSER_H -#define ICARUSALG_UTILITIES_ASSNSCROSSER_H - -// LArSoft libraries -#include "larcorealg/CoreUtils/DebugUtils.h" // lar::debug::demangle() -#include "larcorealg/CoreUtils/enumerate.h" - -// framework libraries -#include "canvas/Persistency/Common/Assns.h" -#include "canvas/Persistency/Common/Ptr.h" -#if defined CANVAS_DEC_VERSION && (CANVAS_DEC_VERSION >= 31100) -# include "canvas/Persistency/Common/ProductPtr.h" -#endif -#include "canvas/Persistency/Provenance/BranchDescription.h" -#include "canvas/Persistency/Provenance/ProductID.h" -#include "canvas/Utilities/InputTag.h" -#include "canvas/Utilities/Exception.h" - -// C/C++ standard libraries -#include // std::set_difference(), std::any_of(), ... -#include // std::mem_fn() -#include // std::move(), std::forward() -#include -#include -#include -#include -#include -#include -#include -#include -#include // std::move_iterator, std::back_inserter -#include // std::logic_error -#include // std::is_constructible_v, std::enable_if_t... -#include -#include - - -namespace icarus::ns::util { - - class InputSpec; - template class StartSpec; - template class StartSpecs; - template class InputSpecs; - template using hopTo = InputSpecs; - template using startFrom = StartSpecs; - template class AssnsCrosser; - - template - AssnsCrosser makeAssnsCrosser - (Event const& event, InputSpecs... inputSpecs); - - template - AssnsCrosser makeAssnsCrosser( - Event const& event, - StartSpecs, InputSpecs... inputSpecs - ); - - std::ostream& operator<< (std::ostream& out, InputSpec const& spec); - template - std::ostream& operator<< (std::ostream& out, StartSpec const& spec); - template - std::ostream& operator<< (std::ostream& out, InputSpecs const& specs); - template - std::ostream& operator<< (std::ostream& out, StartSpecs const& specs); - - namespace details { - template class SpecBase; - template class InputSpecsBase; - template class AssnsMap; - template class AssnsCrosserTypes; - template struct PointerSelector; - using SupportedInputSpecs = std::variant< - std::monostate - , art::InputTag - , art::ProductID - >; - template - using SupportedStartSpecs = std::variant< - std::monostate - , art::InputTag - , art::ProductID - , art::Ptr -#if defined CANVAS_DEC_VERSION && (CANVAS_DEC_VERSION >= 531100) - , art::ProductPtr -#endif - , std::vector> - >; - - } // // namespace icarus::ns::util::details - -} // namespace icarus::ns::util - -// ----------------------------------------------------------------------------- -/** - * @brief Builds multi-hop one-to-many associations from associated pairs. - * @tparam Key the type of the data to associate to - * @tparam OtherTypes intermediate types to reach the target type (the last one) - * - * This class facilitates the crossing of multi-level associations. - * For example, suppose the data contains associations between hits - * (`recob::Hit`) and tracks (`recob::Track`) and between tracks and particle - * flow objects (`recob::PFParticle`). Starting from a particle flow object - * (_PFO_ from now on), we want to know which hits it is associated to. - * We need therefore to cross and join two associations. This problem is solved - * by using a - * `icarus::ns::util::AssnsCrosser` - * object. - * - * This class supports any number of indirections ("hops"). - * - * One major issue in establishing the chain is to find the relevant association - * data products. There are one key type (`Key`) and one or more types to - * hop through until the target type is reached (`OtherTypes`, the last one of - * which is the target type). Assuming there are _N_ types listed in - * `OtherTypes`, and calling `Target` the last one (`OtherTypes[N-1]`), - * there are as well _N_ hops to follow: - * `Key` → `OtherTypes[0]`, `OtherTypes[0]` → `OtherTypes[1]`, ... - * up to `OtherTypes[N-2]` → `Target`. - * The interface of this object requires some information for each of the _N_ - * hops. - * - * Currently the following patterns are supported: - * - * * the data product input tag of all associations are known in advance, - * and there is only one of them. This is the simplest case for - * implementation. On the other end it may be hard for the user to know which - * are all the involved input tags, and the requirement of having a single - * association data product for each hop may be a deal-breaker. - * * the data product input tag of all associations are known in advance, - * and there may be more of them per hop. This is the case with fewest - * assumptions. As in the previous case, it may be hard for the user to know - * which are all the involved input tags. - * * the data product of the first association is known, but not all the others - * are. In that case, one assumption can be that the relevant associations - * are created by the same module and with the same label as the data product - * at the right side of the association. In the example above, this situation - * translates into knowing the tag for the track/PFO association, but not the - * hit/track one; and then the hit/track associations would be assumed to - * have been created by the same module, and with the same tag, which also - * created the track collection (because `recob::Track` is the object on the - * right of the known track/PFO association). This case is currently - * supported only when that assumption holds; otherwise, the behaviour is - * undefined. - * * the data product of the last association is known, but not all the others - * are. The assumption here may be the mirror of the one in the previous - * point. In the example above, this situation translates into knowing the - * tag for the hit/track association, but not the the track/PFO one; and then - * the track/PFO associations would be assumed to have been created by the - * same module, and with the same tag, which also created the PFO collection - * (which we know to be not likely). This case is currently supported only - * when that assumption holds; otherwise, the behaviour is undefined. - * * the data product of the starting data product (strictly speaking not the - * first association) is known, but not all the association data product tags - * are. This case is similar to the case where the first association was - * known, as described above, and it is supported under the same assumptions, - * which in this case extend to the first association as well. - * - * The result can be limited to a selected list of key entries by listing the - * desired elements in the first constructor argument (see `StartSpecs`, alias - * `startFrom`). This mirrors the feature of `art::FindXxx`, but keep in mind - * that while there the lookup is by index of the start list, in this object - * the lookup is by pointer. This also implicitly quenches duplicates in the - * input list, and there is no guarantee that the associations are presented - * in the same key order as the start list (in fact, the order of the results - * is not even defined in this object). - * - * - * ### Many-to-one associations - * - * The support for one-to-many associations in the hopping direction is full. - * In the presence of many-to-one associations, there are some things to be - * kept in mind. - * - * In the case of many-to-one associations, the same target object may appear - * associated to several keys. - * - * The list of target objects associated to a key has an unspecified order and - * it _can_ contain duplicates. For example, in a "diamond" association: - * - * B1 - * / \ - * A1 C1 - * \ / - * B2 - * - * that is an association `A1` ↔ `B1`, `A1` ↔ `B2`, - * `C1` ↔ `B1` and `C1` ↔ `B2`, `C1` will appear in the list of - * `C`s associated with `A1` twice, because there are two paths connecting - * `A1` and `C1`. - * - * - * ### Comparison with `art::FindManyP` - * - * Both `art::FindManyP` and `icarus::ns::util::AssnsCrosser`: - * * support two directly associated data products - * (but then there is little reason to use `AssnsCrosser` over `FindManyP`). - * * precompute all the information at construction, so they are better - * instantiated once. - * * yield for each associated key a vector of _art_ pointers to the associated - * target elements. - * * support a generic `art::Event`-like interface, including (in principle) - * `gallery::Event`. - * - * Differences include: - * * `AssnsCrosser` interface only covers the functionality of `art::FindManyP` - * and `art::FindOneP`, not `art::FindMany`. - * * of course, `AssnsCrosser` supports _indirectly_ associated data products. - * * the assumption on whether an association is one-to-one or one-to-many - * is reflected in which `art::FindXxx` class is chosen (respectively - * `art::FindOneP` and `art::FindManyP`), while here the same class - * `AssnsCrosser` is used, and the assumption is reflected on whether - * `assPtr()` or `assPtrs()` method is used for the query. - * * `AssnsCrosser` indexes by _art_ pointer of the key, while `art::FindManyP` - * indexes by the position of the key in the list specified as input (which - * is bound to match the pointer `key()` when a whole handle is specified as - * input). - * * `AssnsCrosser` does not support target metadata (the metadata on the - * intermediate hops is not relevant, since _art_ can use an association - * with metadata in place of one without, ignoring the metadata itself). - * This feature does not fundamentally conflict with the implementation, but - * neither the interface (presumably similar to `art::FindManyP`) nor the - * implementation were developed. - * - * - * Examples - * --------- - * - * ### Setting up a association crosser object - * - * Let's assume we have three data types, `DataTypeA` associated with - * `DataTypeB` and the latter associated with `DataTypeC`. - * The goal is to have the direct association from `DataTypeA` to `DataTypeC`. - * - * If it is known that the associations between `DataTypeA` and `DataTypeB` - * are all stored in data product tag `"B"` and the associations between - * `DataTypeB` and `DataTypeC` are all stored in data product tag `"C"`, - * the following initializations will work: - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} - * icarus::ns::util::AssnsCrosser const AtoC - * { event, art::InputTag{ "B" }, art::InputTag{ "C" } }; - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * or - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} - * using icarus::ns::util::hopTo; - * auto const AtoC = icarus::ns::util::makeAssnsCrosser - * (event, hopTo{ "B" }, hopTo{ "C" }); - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * or - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} - * using icarus::ns::util::startFrom, icarus::ns::util::hopTo; - * icarus::ns::util::AssnsCrosser const AtoC{ event - * , startFrom{} - * , hopTo{ "B" } - * , hopTo{ "C" } - * }; - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * or - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} - * using icarus::ns::util::startFrom, icarus::ns::util::hopTo; - * auto const AtoC = makeAssnsCrosser(event - * , startFrom{} - * , hopTo{ "B" } - * , hopTo{ "C" } - * ); - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * The latter describe more clearly the relation between the data types and - * their input tags. - * - * If there are two sets of associations between `DataTypeA` and `DataTypeB`, - * `"B:1"` and `"B:2"`, the following initializations will work: - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} - * icarus::ns::util::AssnsCrosser const AtoC - * { event, { "B:1", "B:2" }, { "C" } }; - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * or - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} - * using icarus::ns::util::hopTo; - * auto const AtoC = icarus::ns::util::makeAssnsCrosser( - * event, - * hopTo{ "B:1", "B:2" }, hopTo{ "C" } - * ); - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * If the associations are needed only for a certain subset of pointers, it is - * possible to specify them, in a way similar to `art::FindManyP`: - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} - * using icarus::ns::util::startFrom, icarus::ns::util::hopTo; - * auto const AtoC = icarus::ns::util::makeAssnsCrosser( - * , startFrom{ ptrA1, ptrA2 } - * , hopTo{ "B" } - * , hopTo{ "C" } - * ); - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * or - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} - * std::vector const selectedAptr { ptrA1, ptrA2 }; - * using icarus::ns::util::startFrom, icarus::ns::util::hopTo; - * auto const AtoC = icarus::ns::util::makeAssnsCrosser( - * , selectedAptr - * , hopTo{ "B" } - * , hopTo{ "C" } - * ); - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * Note that only a collection of type `std::vector>` is - * supported (for example, `art::PtrVector` would not be). - * - * - * ### Using a association crosser object - * - * As mentioned in the introduction, `AssnsCrosser` objects differ from - * `art::FindManyP` in that they are queried via _art_ pointers rather than - * indices. - * - * The interface for the query is `assPtr()` (see its documentation for - * examples). - * - */ -template -class icarus::ns::util::AssnsCrosser - : public details::AssnsCrosserTypes -{ - - using This_t = AssnsCrosser; - - public: - - using KeyPtr_t = typename This_t::KeyPtr_t; - using TargetPtr_t = typename This_t::TargetPtr_t; - using TargetPtrs_t = typename This_t::TargetPtrs_t; - - /** - * @brief Constructor: reads and joins the specified associations. - * @tparam Event type to read the data from (`art::Event` interface) - * @param event data source - * @param otherInputSpecs input specifications for all the hops - * - * The associations are read and joined reading the data from `event`. - * - * There needs to be one input specification for each hop, the first - * specification being the one from the key to the first intermediate object - * type. - */ - template - AssnsCrosser - (Event const& event, InputSpecs... otherInputSpecs); - - /** - * @brief Constructor: reads and joins the specified associations. - * @tparam Event type to read the data from (`art::Event` interface) - * @param event data source - * @param startSpec specifies which type to start hopping from - * @param otherInputSpecs input specifications for all the hops - * - * This constructor acts exactly like - * `AssnsCrosser(Event const&, InputSpecs...)`, but the additional - * argument allows C++ to fully determine the type of the object from the - * arguments, thus allowing the direct initialization syntax where data types - * are specified only once and close to their input specification: - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} - * using icarus::ns::util::startFrom, icarus::ns::util::hopTo; - * icarus::ns::util::AssnsCrosser const AtoC{ event - * , startFrom{} - * , hopTo{ "B" } - * ); - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - */ - template - AssnsCrosser( - Event const& event, - StartSpecs startSpec, - InputSpecs... otherInputSpecs - ); - - /** - * @brief Returns pointers to all target objects associated to `keyPtr`. - * @param keyPtr pointer to the key object to find the associated objects of - * @return a list pointers to all target objects associated to `keyPtr` - * - * This query supports a one-to-many association. - * If the `keyPtr` is unknown (either because it's not a valid object in this - * context, or because the pointed object does not have any associated target - * object) an empty collection is returned. - * - * In this example, associating `DataTypeA` objects to `DataTypeC` ones, - * all objects of type `DataTypeA` are tried one after the other: - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} - * icarus::ns::util::AssnsCrosser const AtoC - * { event, art::InputTag{ "B" }, art::InputTag{ "C" } }; - * - * auto const& Ahandle = event.getValidHandle>("B"); - * - * for (std::size_t iA = 0; iA < Ahandle->size(); ++iA) { - * - * art::Ptr const Aptr{ Ahandle, iA }; - * - * std::vector> const& Cptrs = AtoC.assPtrs(Aptr); - * - * // ... - * } // for - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * In the loop, a _art_ pointer to each `DataTypeA` object is created in order - * to query the associated `DataTypeC`. See also `assPtr()` for further usage - * patterns common to the two methods. - */ - TargetPtrs_t const& assPtrs(KeyPtr_t const& keyPtr) const - { return fAssnsMap.assPtrs(keyPtr); } - - /** - * @brief Returns a pointer to the target object associated to `keyPtr`. - * @param keyPtr pointer to the key object to find the associated objects of - * @return a pointer to the target object associated to `keyPtr` - * @throw art::Exception (code: `art::errors::LogicError`) if there are more - * than one target pointer associated to the specified key - * - * This query assumes a one-to-one association. - * If the `keyPtr` is unknown (either because it's not a valid object in this - * context, or because the pointed object does not have any associated target - * object) the pointer is returned null. - * If there are more than one elements associated with the key, - * an exception is thrown. - * - * In this example, associating `DataTypeA` objects to `DataTypeC` ones, - * all objects of type `DataTypeA` are tried one after the other: - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} - * icarus::ns::util::AssnsCrosser const AtoC - * { event, art::InputTag{ "B" }, art::InputTag{ "C" } }; - * - * auto const& Ahandle = event.getValidHandle>("B"); - * - * for (std::size_t iA = 0; iA < Ahandle->size(); ++iA) { - * - * art::Ptr const Aptr{ Ahandle, iA }; - * - * art::Ptr const Cptr = AtoC.assPtr(Aptr); - * - * // ... - * } // for - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * In the loop, a _art_ pointer to each `DataTypeA` object is created in order - * to query the associated `DataTypeC`. While this may add complications in - * the simplest case (as in the example), it allows for mixing multiple input - * collections (e.g. trying to cross an associations of tracks stored with one - * data product per cryostat to CRT hits stored in a single data product), and - * to pass a selection of objects (via _art_ pointers): - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} - * icarus::ns::util::AssnsCrosser const AtoC - * { event, art::InputTag{ "B" }, art::InputTag{ "C" } }; - * - * auto const& Ahandle = event.getValidHandle>("B"); - * - * for (art::Ptr const& Aptr: selectDataA(Ahandle)) { - * - * art::Ptr const Cptr = AtoC.assPtr(Aptr); - * - * // ... - * } // for - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * (with - * `template std::vector> selectDataA(Handle const& handle)` - * some selection function, templated to `Handle` to support both - * `art::Handle` and `art::ValidHandle`). - * - */ - TargetPtr_t const& assPtr(KeyPtr_t const& keyPtr) const; - - - private: - - using Key_t = typename This_t::Key_t; - using Target_t = typename This_t::Target_t; - - using AssnsMap_t = details::AssnsMap; - - /// Which algorithm to use for traversing the associations. - enum class HoppingAlgo { forward, backward }; - - AssnsMap_t fAssnsMap; ///< Associated objects per key. - - - static TargetPtr_t const NullTargetPtr; ///< Used as return reference value. - - - /// Returns the full content of the association map. - template - AssnsMap_t prepare( - Event const& event, - StartSpecs startSpecs, InputSpecs... otherInputSpecs - ) const; - - /// Determines which algorithm should be used for association traversal. - HoppingAlgo chooseTraversalAlgorithm( - StartSpecs const& startSpecs, - InputSpecs const&... otherInputSpecs - ) const; - - /// Returns a list of relevant pointers from the start specifications. - template - std::optional> keysFromSpecs - (Event const& event, StartSpecs const& specs) const; - -}; // icarus::ns::util::AssnsCrosser - - -// ----------------------------------------------------------------------------- -/// Wrapper to specify a single source of an association. -template -class icarus::ns::util::details::SpecBase: public SupportedVariants { - using SupportedVariants::SupportedVariants; - - public: - using SupportedSpecs_t = SupportedVariants; - - /// Returns the specification (as a variant). - // Newer C++17 revision won't need this. - SupportedSpecs_t const& spec() const { return *this; } - - protected: - - struct HasSpecTest { - - bool operator() (std::monostate) const { return false; } - bool operator() (art::ProductID id) const { return id != art::ProductID{}; } - bool operator() (art::InputTag const& tag) const { return !tag.empty(); } - - }; // HasSpecTest - -}; // icarus::ns::util::SpecBase - - -// ----------------------------------------------------------------------------- -/// Wrapper to specify a single source of an association. -class icarus::ns::util::InputSpec - : public details::SpecBase -{ - using Base_t = details::SpecBase; - using Base_t::Base_t; - - public: - - bool hasSpec() const noexcept; - -}; // icarus::ns::util::InputSpec - - -// ----------------------------------------------------------------------------- -/// Wrapper to specify a single source of an association. -template -class icarus::ns::util::StartSpec - : public details::SpecBase> -{ - using Base_t = details::SpecBase>; - using Base_t::Base_t; - - public: - using Key_t = T; - - bool hasSpec() const noexcept; - -}; // icarus::ns::util::StartSpec - - -// ----------------------------------------------------------------------------- -template -class icarus::ns::util::details::InputSpecsBase - : private std::vector // saved list of specifications -{ - using Specs_t = std::vector; - - public: - using Spec_t = SpecType; - - /// Constructor: single input specification (whatever can construct it). - template < - typename... Args, - typename = std::enable_if_t - > - > - InputSpecsBase(Args&&... specArgs) - : Specs_t{ Spec_t{ std::forward(specArgs)... } } {} - - /// Constructor: a list of input specifications. - InputSpecsBase(std::initializer_list specs) - : Specs_t - { std::move_iterator{ specs.begin() }, std::move_iterator{ specs.end() } } - {} - - /// Constructor: a list of input specifications. - InputSpecsBase(std::vector specs) - : Specs_t(std::move(specs)) {} - - /// Returns whether at least one of the specs specifies an input. - bool hasSpecs() const noexcept; - - /// Returns whether at least one of the specs specifies no input. - bool hasEmptySpecs() const noexcept; - - using Specs_t::empty; - using Specs_t::size; - using Specs_t::begin; - using Specs_t::end; - using Specs_t::cbegin; - using Specs_t::cend; - using Specs_t::at; - using Specs_t::operator[]; - -}; // icarus::ns::util::details::InputSpecsBase - - -// ----------------------------------------------------------------------------- -/** - * @brief Wrapper to specify the key type for the association hops. - * @tparam T type of the association hop these specifications refer to - * - * There are several ways to specify the content of a start list; - * all of them require the explicit specification of the type `T` - * (however the full type can be sometimes deduced in `AssnsCrosser` constructor - * call or `makeAssnsCrosser()` function). - * If the start list is empty or includes only invalid entries (like null - * pointers, empty input tags, invalid product IDs) it is assumed that all the - * pointers in the deduced input set are desired. - * - * Several input types are supported: the same ones as in `InputSpecs`, - * plus `art::Ptr`, `std::vector>` and `art::ProductPtr`. - * - * Examples in conjunction with `AssnsCrosser` and `makeAssnsCrosser()`, - * assuming `event` and `DataType`s being a data source object and data types: - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} - * using icarus::ns::util::StartSpecs; - * - * std::vector const Aptrs{ ptrA1, ptrA2 }; - * - * // all A entries found in the "B" associations - * icarus::ns::util::AssnsCrosser const AtoC_1 - * { event, StartSpec{}, "B", "C" }; - * - * // all A entries found in the "B" associations, same as above - * icarus::ns::util::AssnsCrosser const AtoC_1 - * { event, {}, "B", "C" }; - * - * // all entries found in the "A" data product - * icarus::ns::util::AssnsCrosser const AtoC_1 - * { event, "A", "B", "C" }; - * - * // only entries pointed by `ptrA1` and `ptrA2` - * icarus::ns::util::AssnsCrosser const AtoC_1 - * { event, { ptrA1, ptrA2 }, "B", "C" }; - * - * // only entries pointed by the pointers in `Aptrs` - * icarus::ns::util::AssnsCrosser const AtoC_1 - * { event, Aptrs, "B", "C" }; - * - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * Note that `startFrom` is available as an alias of `StartSpecs`, with exactly - * the same semantics and syntax. - */ -template -class icarus::ns::util::StartSpecs - : public details::InputSpecsBase> -{ - using details::InputSpecsBase>::InputSpecsBase; -}; - - -// ----------------------------------------------------------------------------- -/** - * @brief Wrapper to specify all the sources of an association. - * @tparam T type of the association hop these specifications refer to - * - * There are several ways to specify the content of the specifications; - * all of them require the explicit specification of the type `T`. - * - * Examples in conjunction with `AssnsCrosser` and `makeAssnsCrosser()`, - * assuming `event` and `DataType`s being a data source object and data types: - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} - * using icarus::ns::util::InputSpecs, icarus::ns::util::InputSpec; - * - * using AtoZ_t = icarus::ns::util::AssnsCrosser< - * DataTypeA, DataTypeB, DataTypeC, DataTypeD, DataTypeE, DataTypeF - * >; - * - * AtoZ_t const AtoZ{ event - * - * // implicit conversion to `art::InputTag`: - * , InputSpecs{ "TagB" } - * - * // implicit conversion to `art::InputTag` then to `InputSpecs`: - * , "TagC" - * - * // explicit vector of input tags (not recommended): - * , InputSpecs{ std::vector{ "TagD1", "TagD2" } } - * - * // list of input tags, converted to `InputSpecs`: - * , InputSpecs{ "TagE1", "TagE2" } - * - * // implicit list of input tags, converted to `InputSpecs`: - * , { "TagF1", "TagF2" } - * - * }; - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * Note that `hopTo` is available as an alias of `InputSpecs`, with exactly the - * same semantics and syntax. - */ -template -class icarus::ns::util::InputSpecs: public details::InputSpecsBase { - using details::InputSpecsBase::InputSpecsBase; -}; - - -// ---------------------------------------------------------------------------- -// --- Implementation -// ---------------------------------------------------------------------------- -namespace icarus::ns::util::details { - - /// The first of the template types. - template - using first_type_t = std::tuple_element_t<0, std::tuple>; - - /// The type with index `N` in the type list reversed from `Ts`. - template - struct end_type { - using type - = std::tuple_element_t>; - }; - - /// Returns the last of `Ts` types. - template - struct last_type { using type = typename end_type<0, Ts...>::type; }; - - template - struct MapJoiner; - - template - std::ostream& operator<< - (std::ostream& out, AssnsMap const& map); - - /// Appends to `dest` a copy of the content of `src`. - template - Dest& append(Dest& dest, Src const& src); - - /// Steals and appends to `dest` the content of `src`. - template - Dest& append(Dest& dest, Src&& src); - - /// Returns a constant reference to the `Index`-th of the `data` arguments. - template - auto const& getElement(Ts const&... data); - - /// Returns a vector with the elements of the sorted `minuend` which - /// are not present in the sorted `subtrahend`. - template - std::vector set_difference - (Minuend const& minuend, Subtrahend const& subtrahend); - - template - std::ostream& operator<< - (std::ostream& out, InputSpecsBase const& specs); - -} // namespace icarus::ns::util::details - - -// ----------------------------------------------------------------------------- -template -class icarus::ns::util::details::AssnsCrosserTypes { - - protected: - - static constexpr std::size_t NOtherTypes = sizeof...(OtherTypes); - static_assert(NOtherTypes >= 1, "AssnsCrosser requires at least two types."); - - public: - - using Key_t = KeyType; - using Target_t = typename last_type::type; - - using KeyPtr_t = art::Ptr; - using TargetPtr_t = art::Ptr; - using TargetPtrs_t = std::vector; - - using Assns_t = art::Assns; - - using AssnsMap_t = std::unordered_map; - -}; // icarus::ns::util::details::AssnsCrosserTypes - - -// ----------------------------------------------------------------------------- -template -class icarus::ns::util::details::AssnsMap - : public details::AssnsCrosserTypes -{ - - public: - - using This_t = AssnsMap; - - using KeyPtr_t = typename This_t::KeyPtr_t; - using TargetPtr_t = typename This_t::TargetPtr_t; - using TargetPtrs_t = typename This_t::TargetPtrs_t; - - using AssnsMap_t = typename This_t::AssnsMap_t; - - - // --- BEGIN -- Modify interface --------------------------------------------- - ///@name Modify interface - ///@{ - - /// Add a `targetPtr` associated to a `keyPtr` (duplicates not checked). - This_t& add(KeyPtr_t const& keyPtr, TargetPtr_t const& targetPtr); - - /// Add all `targetPtrs` associated to a `keyPtr` (duplicates not checked). - This_t& add(KeyPtr_t const& keyPtr, TargetPtrs_t const& targetPtrs); - - /// Add all `targetPtrs` associated to a `keyPtr` (duplicates not checked). - /// The content of `targetPtrs` is lost. - This_t& add(KeyPtr_t const& keyPtr, TargetPtrs_t&& targetPtrs); - - /// Returns the pointers associated to `keyPtr` (empty if none). - /// The key is not associated with them any more. - TargetPtrs_t yieldAssPtrs(KeyPtr_t const& keyPtr); - - /// Returns an iterable of pairs key/targets, which can be modified - /// (do **not** modify the key value!). - AssnsMap_t& assnsMap() { return fAssnsMap; } - - /// Removes all the stored associations and keys. - void clear() { fAssnsMap.clear(); } - - ///@} - // --- END ---- Modify interface --------------------------------------------- - - - // --- BEGIN -- Query interface ---------------------------------------------- - ///@name Query interface - ///@{ - - /// Returns whether there is data in the map. - bool empty() const noexcept { return fAssnsMap.empty(); } - - /// Returns the pointers associated to `keyPtr` (empty if none). - TargetPtrs_t const& assPtrs(KeyPtr_t const& keyPtr) const; - - /// Returns a map of key pointers to a sequence of associated target pointers. - AssnsMap_t const& assnsMap() const { return fAssnsMap; } - - /// Returns a sorted list of all the product IDs in the key pointers. - std::vector keyProductIDs() const; - - /// Returns a sorted list of all the product IDs in the target pointers. - std::vector targetProductIDs() const; - - ///@} - // --- END ---- Query interface ---------------------------------------------- - - - /// Returns a map from target to key describing the same associations as this. - AssnsMap flip() const; - - - private: - - static TargetPtrs_t const EmptyColl; - - AssnsMap_t fAssnsMap; ///< Key pointer -> all associated target pointers. - -}; // icarus::ns::util::details::AssnsMap - - -// ----------------------------------------------------------------------------- -/// Instructions on which pointers of type T to select. -template -struct icarus::ns::util::details::PointerSelector { - using Ptr_t = art::Ptr; - - PointerSelector(std::vector ptrs, std::vector IDs); - - bool operator() (Ptr_t const& ptr) const; - - private: - std::vector fPtrs; ///< Listed pointers pass. - std::vector fIDs; ///< All objects with these ID pass. - -}; // icarus::ns::util::details::PointerSelector - - -// ----------------------------------------------------------------------------- -// --- template implementation -// ----------------------------------------------------------------------------- -template -Dest& icarus::ns::util::details::append(Dest& dest, Src const& src) { - using std::begin, std::end; - dest.insert(end(dest), begin(src), end(src)); - return dest; -} // icarus::ns::util::details::append(Src const&) - - -template -Dest& icarus::ns::util::details::append(Dest& dest, Src&& src) { - using std::empty, std::begin, std::end; - if (empty(dest)) dest = std::move(src); - else { - dest.insert( - end(dest), std::move_iterator(begin(src)), std::move_iterator(end(src)) - ); - src.clear(); - } - return dest; -} // icarus::ns::util::details::append(Src&&) - - -// ----------------------------------------------------------------------------- -template -auto const& icarus::ns::util::details::getElement(Ts const&... data) { - - auto access = std::forward_as_tuple(data...); - - constexpr std::size_t index = (Index < 0)? sizeof...(data) + Index: Index; - return std::get(access); - -} // icarus::ns::util::details::getElement() - - -// ----------------------------------------------------------------------------- -template -std::vector -icarus::ns::util::details::set_difference - (Minuend const& minuend, Subtrahend const& subtrahend) -{ - using std::begin, std::end; - std::vector diff; - std::set_difference( - begin(minuend), end(minuend), begin(subtrahend), end(subtrahend), - back_inserter(diff) - ); - return diff; -} // icarus::ns::util::details::set_difference() - - -// ----------------------------------------------------------------------------- -// --- icarus::ns::util::details::AssnsMap -// ----------------------------------------------------------------------------- -template -typename icarus::ns::util::details::AssnsMap::TargetPtrs_t -const icarus::ns::util::details::AssnsMap::EmptyColl; - - -// ----------------------------------------------------------------------------- -template -auto icarus::ns::util::details::AssnsMap::add - (KeyPtr_t const& keyPtr, TargetPtr_t const& targetPtr) -> This_t& - { fAssnsMap[keyPtr].push_back(targetPtr); return *this; } - - -// ----------------------------------------------------------------------------- -template -auto icarus::ns::util::details::AssnsMap::add - (KeyPtr_t const& keyPtr, TargetPtrs_t const& targetPtrs) -> This_t& -{ - append(fAssnsMap[keyPtr], targetPtrs); - return *this; -} // icarus::ns::util::details::AssnsMap<>::add(TargetPtrs_t&) - - -// ----------------------------------------------------------------------------- -template -auto icarus::ns::util::details::AssnsMap::add - (KeyPtr_t const& keyPtr, TargetPtrs_t&& targetPtrs) -> This_t& -{ - append(fAssnsMap[keyPtr], std::move(targetPtrs)); - return *this; -} // icarus::ns::util::details::AssnsMap<>::add(TargetPtrs_t&&) - - -// ----------------------------------------------------------------------------- -template -auto icarus::ns::util::details::AssnsMap::assPtrs - (KeyPtr_t const& keyPtr) const -> TargetPtrs_t const& -{ - auto const it = fAssnsMap.find(keyPtr); - return (it == fAssnsMap.end())? EmptyColl: it->second; -} // icarus::ns::util::details::AssnsMap<>::assPtrs() - - -// ----------------------------------------------------------------------------- -template -auto icarus::ns::util::details::AssnsMap::keyProductIDs() - const -> std::vector -{ - std::vector IDs; - for (auto const& pairs: fAssnsMap) { - art::ProductID const ID = pairs.first.id(); - if (std::find(IDs.rbegin(), IDs.rend(), ID) == IDs.rend()) - IDs.push_back(ID); - } // for - std::sort(IDs.begin(), IDs.end()); - return IDs; -} // icarus::ns::util::details::AssnsMap::keyProductIDs() - - -// ----------------------------------------------------------------------------- -template -auto icarus::ns::util::details::AssnsMap::targetProductIDs - () const -> std::vector -{ - std::vector IDs; - for (auto const& pairs: fAssnsMap) { - for (art::Ptr const& ptr: pairs.second) { - art::ProductID const ID = ptr.id(); - if (std::find(IDs.rbegin(), IDs.rend(), ID) == IDs.rend()) - IDs.push_back(ID); - } // for pointers - } // for pairs - std::sort(IDs.begin(), IDs.end()); - return IDs; -} // icarus::ns::util::details::AssnsMap::targetProductIDs() - - -// ----------------------------------------------------------------------------- -template -auto icarus::ns::util::details::AssnsMap::yieldAssPtrs - (KeyPtr_t const& keyPtr) -> TargetPtrs_t -{ - auto it = fAssnsMap.find(keyPtr); - return (it == fAssnsMap.end()) - ? EmptyColl: std::exchange(it->second, TargetPtrs_t{}); -} // icarus::ns::util::details::AssnsMap<>::yieldAssPtrs() - - -// ----------------------------------------------------------------------------- -template -auto icarus::ns::util::details::AssnsMap::flip() const - -> AssnsMap -{ - // brute force - AssnsMap map; - for (auto const& [ key, targets ]: fAssnsMap) { - for (art::Ptr const& target: targets) { - map.add(target, key); - } // for key targets - } // for source keys - return map; -} // icarus::ns::util::details::AssnsMap<>::flip() - - -// ----------------------------------------------------------------------------- -template -std::ostream& icarus::ns::util::details::operator<< - (std::ostream& out, AssnsMap const& map) -{ - auto const& assnsMap = map.assnsMap(); - if (assnsMap.empty()) { - out << "no association"; - } - else { - out << "associations:"; - std::size_t nTargets = 0; - for (auto const& [ key, targets ]: assnsMap) { - out << "\n " << key << ": " << targets.size() << " associated targets"; - if (targets.empty()) continue; - nTargets += targets.size(); - for (auto const& [ iTarget, target ]: ::util::enumerate(targets)) - out << "\n [" << iTarget << "] " << target; - } // for keys - out << "\n" - << assnsMap.size() << " keys associated to " << nTargets << " targets"; - } // if ... else - return out; -} // icarus::ns::util::details::operator<< (AssnsMap) - - -// ----------------------------------------------------------------------------- -// --- icarus::ns::util::details::PointerSelector -// ----------------------------------------------------------------------------- -template -icarus::ns::util::details::PointerSelector::PointerSelector - (std::vector ptrs, std::vector IDs) - : fPtrs{ std::move(ptrs) }, fIDs{ std::move( IDs ) } -{ - std::sort(fPtrs.begin(), fPtrs.end()); - std::sort(fIDs.begin(), fIDs.end()); -} - - -// ----------------------------------------------------------------------------- -template -bool icarus::ns::util::details::PointerSelector::operator() - (Ptr_t const& ptr) const -{ - if (std::binary_search(fIDs.begin(), fIDs.end(), ptr.id())) return true; - return std::binary_search(fPtrs.begin(), fPtrs.end(), ptr); -} - - -// ----------------------------------------------------------------------------- -// --- icarus::ns::util::InputSpec and related -// ----------------------------------------------------------------------------- -bool icarus::ns::util::InputSpec::hasSpec() const noexcept { - - struct HasInputSpecTest: HasSpecTest {}; - - return std::visit(HasInputSpecTest{}, spec()); -} // icarus::ns::util::InputSpec::hasSpec() - - -// ----------------------------------------------------------------------------- -template -bool icarus::ns::util::StartSpec::hasSpec() const noexcept { - - struct HasStartSpecTest: Base_t::HasSpecTest { - using Base_t::HasSpecTest::operator(); - bool operator() (art::Ptr const& ptr) const - { return ptr.isNonnull(); } -#if defined CANVAS_DEC_VERSION && (CANVAS_DEC_VERSION >= 531100) - bool operator() (art::ProductPtr const& ptr) const - { return (*this)(ptr.id()); } -#endif - bool operator() (std::vector> const& ptrs) const - { - return std::any_of - (ptrs.begin(), ptrs.end(), std::mem_fn(&art::Ptr::isNonnull)); - } - }; - - return std::visit(HasStartSpecTest{}, Base_t::spec()); -} // icarus::ns::util::StartSpec<>::hasSpec() - - -// ----------------------------------------------------------------------------- -template -bool icarus::ns::util::details::InputSpecsBase::hasSpecs - () const noexcept -{ - return std::any_of(begin(), end(), std::mem_fn(&SpecType::hasSpec)); -} - - -// ----------------------------------------------------------------------------- -template -bool icarus::ns::util::details::InputSpecsBase::hasEmptySpecs - () const noexcept -{ - return !std::all_of(begin(), end(), std::mem_fn(&SpecType::hasSpec)); -} - - -// ----------------------------------------------------------------------------- -inline std::ostream& icarus::ns::util::operator<< - (std::ostream& out, InputSpec const& spec) -{ - struct InputSpecDumper { - std::ostream& out; - - InputSpecDumper(std::ostream& out): out(out) {} - - void operator() (std::monostate) const - { out << "autodetect"; } - void operator() (art::InputTag const& tag) const - { out << "tag '" << tag.encode() << "'"; } - void operator() (art::ProductID const& ID) const - { out << "ProdID=" << ID; } - - }; // InputSpecDumper - - std::visit(InputSpecDumper{ out }, spec.spec()); - return out; - -} // icarus::ns::util::operator<< (InputSpec) - - -// ----------------------------------------------------------------------------- -template -inline std::ostream& icarus::ns::util::details::operator<< - (std::ostream& out, InputSpecsBase const& specs) -{ - std::size_t const nSpecs = specs.size(); - - if (nSpecs == 0) { - out << "no specs"; - return out; - } - - std::size_t iSpec = 0; - if (nSpecs > 1) - out << specs.size() << " specs: [0] "; - out << "{ " << specs[iSpec] << " }"; - while (++iSpec < nSpecs) { - out << "[" << iSpec << "] {" << specs[iSpec] << "}"; - } - - return out; -} // icarus::ns::util::details::operator<< (InputSpecsBase) - - -// ----------------------------------------------------------------------------- -template -std::ostream& icarus::ns::util::operator<< - (std::ostream& out, InputSpecs const& specs) -{ - out << "() << "> " - << static_cast const&>(specs); - return out; -} // icarus::ns::util::details::operator<< (InputSpecs) - - -// ----------------------------------------------------------------------------- -template -std::ostream& icarus::ns::util::operator<< - (std::ostream& out, StartSpecs const& specs) -{ - out << "() << "> " - << static_cast const&>(specs); - return out; -} // icarus::ns::util::details::operator<< (StartSpecs) - - -// ----------------------------------------------------------------------------- -// --- icarus::ns::util::details::MapJoiner -// ----------------------------------------------------------------------------- -template -struct icarus::ns::util::details::MapJoiner { - - // the first and other hop types are kept separate because it's hard - // to split the parameter pack of the others from the complete one otherwise - - using TargetType = typename last_type::type; - - struct NoSelector_t - { template bool operator() (T const&) const { return true; } }; - - static constexpr std::size_t nHops = 1 + sizeof...(OtherHopTypes); - - static constexpr NoSelector_t NoSelector{}; - - /** - * @brief Returns a association map from `KeyType` to `TargetType`. - * @tparam Event a data repository (`art::Event`-like interface) - * @param event the event to read the associations from - * @param firstHopInputSpec specification for the first hop associations - * @param otherHopInputSpec specification for all other hop associations - * @return a association map from `KeyType` to `TargetType` - * - * The algorithm starts from the last hop (associations from the - * previous-to-last of `OtherHopTypes` to the `TargetType`) and attached the - * associations hopping backward. - * - * This algorithm is slightly simpler than the forward one. - */ - template - static AssnsMap joinBackward( - Event const& event, - InputSpecs firstHopInputSpec, - InputSpecs... otherHopInputSpecs - ) { - - if constexpr(nHops == 1) { - std::vector const firstHopTags - = extractTagList(std::move(firstHopInputSpec), event); - return assnsToMap(event, firstHopTags); - } - else { - // 1 is the first hop (KeyType -> FirstHopType), - // 2 is all the others (FirstHopType -> TargetType) - auto assnsMap2 = MapJoiner::joinBackward - (event, std::move(otherHopInputSpecs)...); - return leftExtendMapWithAssns - (std::move(assnsMap2), event, std::move(firstHopInputSpec)); - } // if more than one hop - } // joinBackward() - - - /** - * @brief Returns a association map from `KeyType` to `TargetType`. - * @tparam Event a data repository (`art::Event`-like interface) - * @tparam Selector functor with `bool operator() const (art::Ptr)` - * @param event the event to read the associations from - * @param firstHopInputSpec specification for the first hop associations - * @param otherHopInputSpec specification for all other hop associations - * @param selector if specified, only keys passing the selector are considered - * @return a association map from `KeyType` to `TargetType` - * - * The algorithm starts from the first hop (associations from the - * `KeyType` to the `FirstHopType`) and attached the associations hopping - * forward. - */ - template - static AssnsMap joinForward( - Event const& event, - InputSpecs firstHopInputSpec, - InputSpecs... otherHopInputSpecs, - std::optional const& selector = NoSelector - ) { - std::vector const firstHopTags - = extractTagList(std::move(firstHopInputSpec), event); - - auto leftMap - = assnsToMap(event, firstHopTags, selector); - - if constexpr(nHops == 1) { - return leftMap; - } - else { - return multiRightExtendMapWithAssns - (std::move(leftMap), event, std::move(otherHopInputSpecs)...); - } - } // joinForward() - - - /// Returns an association map from `tag` associations read from `event`. - /// Only left entries passing `selector` are included. - template < - typename Left, typename Right, - typename Event, typename InputTags, typename Selector = NoSelector_t - > - static AssnsMap assnsToMap( - Event const& event, InputTags const& tags, - std::optional const& selector = std::nullopt - ) { - AssnsMap assnsMap; - for (art::InputTag const& tag: tags) - addAssnsToMap(assnsMap, event, tag, selector); - return assnsMap; - } // assnsToMap() - - /// Extends the association `map` with `tag` associations read from `event`, - /// adding only the ones with left pointer listed in `selector`. - template < - typename Left, typename Right, - typename Event, typename Selector = NoSelector_t - > - static AssnsMap& addAssnsToMap( - AssnsMap& map, Event const& event, art::InputTag const& tag, - std::optional const& selector = std::nullopt - ) { - return addAssnsToMap( - map, event.template getProduct>(tag), selector - ); - } - - /// Extends the association `map` with the specified _art_ associations, - /// adding only the ones with left pointer passing `selector`. - template - static AssnsMap& addAssnsToMap( - AssnsMap& map, art::Assns const& assns, - std::optional const& selector = std::nullopt - ) { - for (auto const& [ leftPtr, rightPtr ]: assns) { - if (!selector || (*selector)(leftPtr)) map.add(leftPtr, rightPtr); - } - return map; - } // addAssnsToMap() - - /** - * @brief Returns a new association map extended on the key side - * @tparam NewLeft (mandatory) type of key for the new map - * @tparam Left type of key of the existing map - * @tparam Right type of target of the existing map - * @tparam Event type of the data repository - * @tparam InputTags type of a collection of `art::InputTag` - * @param map the map to be "extended"; it will be depleted of its content - * @param event the data repository to read the associations from - * @param specs the specification for the input of the extending association - * @return a new association map - * - * The returned association map has `NewLeft` as the new key type and the same - * target type (`Right`) as the input `map`. - * The resulting map is joining the key of the input `map` with the target of - * the associations being read. - * The map _may_ come out smaller than the two inputs. - */ - template < - typename NewLeft, typename Left, typename Right, typename Event, typename T - > - static AssnsMap leftExtendMapWithAssns( - AssnsMap&& map, Event const& event, InputSpecs specs) { - // read the associations with the material for the extension - bool const bAutodetect = specs.hasEmptySpecs(); - std::vector const tags - = extractTagList(std::move(specs), event); - std::vector neededIDs; - if (bAutodetect) neededIDs = map.keyProductIDs(); - auto const leftMap = mapExtensionPreparation - (event, tags, std::move(neededIDs)); - return joinMaps(leftMap, std::move(map)); - } // leftExtendMapWithAssns() - - - template < - typename Left, typename Right, typename NextRight, typename... MoreRights, - typename Event - > - static AssnsMap::type> - multiRightExtendMapWithAssns( - AssnsMap&& map, - Event const& event, - InputSpecs nextInputSpec, - InputSpecs... otherInputSpec - ) - { - AssnsMap assnsMap = rightExtendMapWithAssns - (std::move(map), event, std::move(nextInputSpec)); - - if constexpr(sizeof...(MoreRights) == 0) { - return assnsMap; - } - else { - return multiRightExtendMapWithAssns - (std::move(assnsMap), event, std::move(otherInputSpec)...); - } - } // multiRightExtendMapWithAssns() - - - /** - * @brief Returns a new association map extended on the target side - * @tparam NewRight (mandatory) type of key for the new map - * @tparam Left type of key of the existing map - * @tparam Right type of target of the existing map - * @tparam Event type of the data repository - * @param map the map to be "extended"; it will be depleted of its content - * @param event the data repository to read the associations from - * @param specs the specification for the input of the extending association - * @return a new association map - * - * The returned association map has `NewRight` as the new target type and the - * same key type (`Left`) as the input `map`. - * The resulting map is joining the key of the association being read with the - * key of the input `map`. - * The map _may_ come out smaller than the two inputs. - */ - template< - typename NewRight, typename Left, typename Right, typename Event, typename T - > - static AssnsMap rightExtendMapWithAssns - (AssnsMap map, Event const& event, InputSpecs specs) - { - // read the associations with the material for the extension - bool const bAutodetect = specs.hasEmptySpecs(); - std::vector const tags - = extractTagList(std::move(specs), event); - std::vector neededIDs; - if (bAutodetect) neededIDs = map.targetProductIDs(); - auto rightMap = mapExtensionPreparation - (event, tags, std::move(neededIDs)); - return joinMaps(map, std::move(rightMap)); - } - - /// Joins two maps in the middle, stealing content from the right one. - template - static AssnsMap joinMaps - (AssnsMap const& leftMap, AssnsMap&& rightMap) - { - AssnsMap map; - for (auto& [ leftPtr, middlePtrs ]: leftMap.assnsMap()) { - for (art::Ptr const& middlePtr: middlePtrs) { - map.add(leftPtr, rightMap.yieldAssPtrs(middlePtr)); - } // for middle pointers - } // for left map - return map; - } // joinMaps() - - - /** - * @brief Collects a map of Left-to-Right pointers. - * @tparam Left type of key in the map - * @tparam Right type of target in the map - * @tparam JointSide `0` for `Left` side, `1` for `Right` side - * @tparam Event type of data repository to read data from (`art::Event` I/F) - * @tparam InputTags type of collection of `art::InputTag` objects - * @param event the event to read the data from - * @param tags the list of input tags to needed `Left`-to`Right` associations - * @param requiredIDs list of product IDs needed for the the extension - * @return a `Left`-to-`Right` association map - * @throw art::Exception (code: `art::errors::ProductNotFound`) if a required - * association is not found - * - * The returned map contains all the `Left`-to-`Right` associations specified - * by `tags`; if any is missing, an exception is thrown. - * - * After these associations are collected, the product ID of the pointers - * in the `JointSide` side are compared with the ones specified in the - * `requiredIDs` list. For each ID which is in `requiredIDs` but does not - * appear in the associations collected so far, the algorithm attempts - * to read another `Left`-to-`Right` association using the exact same input - * tag as the one associated to that product ID. If such association data - * product is found, its content is added to the map. Otherwise, the algorithm - * moves on, not considering this a fatal error. - * - * The only clear fatal error condition tested by this algorithm is when no - * mandatory tag is specified, some IDs are present in `requiredIDs` _and_ - * no data product has been found from any of them. In that case, an exception - * is thrown (still `art::errors::ProductNotFound` code). - */ - template < - typename Left, typename Right, std::size_t JointSide, - typename Event, typename InputTags - > - static AssnsMap mapExtensionPreparation( - Event const& event, InputTags const& tags, - std::vector const& requiredIDs - ) { - /* - * First read all the associations with tags that are explicitly tagged; - * then compare their ID with the IDs that we are required. - * For each required ID not present in the original tags, - * an association is read (failure is not an error). - */ - using Assns_t = AssnsMap; - Assns_t map = assnsToMap(event, tags); - - constexpr auto extractProductIDs = (JointSide == 0) - ? &Assns_t::keyProductIDs: &Assns_t::targetProductIDs; - std::vector const mapIDs = (map.*extractProductIDs)(); - - std::vector const missingIDs - = details::set_difference(requiredIDs, mapIDs); - - unsigned int nDiscovered = 0; - for (art::ProductID const ID: missingIDs) { - art::InputTag const tag = getInputTag(event, ID); - auto handle = event.template getHandle>(tag); - if (!handle) continue; - addAssnsToMap(map, *handle); - ++nDiscovered; - } // for - - // error check for an extreme case: - using std::empty; - if (empty(tags) && !missingIDs.empty() && (nDiscovered == 0)) { - std::string const leftName = lar::debug::demangle(); - std::string const rightName = lar::debug::demangle(); - // even if this error is not triggered we may still be missing some - throw art::Exception{ art::errors::ProductNotFound } - << "During preparation of " << leftName << " <=> " << rightName - << " associations to join on " - << ((JointSide == 0)? leftName: rightName) - << " couldn't find any of the needed association data products!" - << " Some must be explicitly specified via input tag." - << "\n"; - } - - return map; - } // mapExtensionPreparation() - - /// Returns the input tag associated to the product `ID` (empty if not found). - template - static art::InputTag getInputTag(Event const& event, art::ProductID ID) - { - art::BranchDescription const* branchDescr - = event.getProductDescription(ID).get(); - if (!branchDescr) return {}; - return { branchDescr->inputTag() }; - } // getInputTag() - - /// Helper returning a list of input tags out of a InputSpec - template - struct TagListExtractor { - Event const* event; - - TagListExtractor(Event const* event): event{ event } {} - - std::vector operator() - (std::monostate) const - { return {}; } - - std::vector operator() - (art::InputTag&& inputTag) const - { return { std::move(inputTag) }; } - - std::vector operator() - (art::ProductID ID) const - { return { getInputTag(*event, ID) }; } - - }; // TagListExtractor - - template - static std::vector extractTagList - (InputSpecs&& inputSpecs, Event const& event) - { - std::vector tags; - for (InputSpec& spec: inputSpecs) { - append(tags, - visit( - TagListExtractor{ &event }, - static_cast(spec) - )); - } // for - return tags; - } - -}; // icarus::ns::util::details::MapJoiner - - -// ----------------------------------------------------------------------------- -// --- icarus::ns::util::AssnsCrosser -// ----------------------------------------------------------------------------- -template -typename icarus::ns::util::AssnsCrosser::TargetPtr_t -const icarus::ns::util::AssnsCrosser::NullTargetPtr; - - -// ----------------------------------------------------------------------------- -template -template -icarus::ns::util::AssnsCrosser::AssnsCrosser( - Event const& event, - InputSpecs... otherInputSpecs -) - : AssnsCrosser{ event, StartSpecs{}, std::move(otherInputSpecs)... } -{} - - -// ----------------------------------------------------------------------------- -template -template -icarus::ns::util::AssnsCrosser::AssnsCrosser( - Event const& event, - StartSpecs startSpecs, - InputSpecs... otherInputSpecs -) - : fAssnsMap - { prepare(event, std::move(startSpecs), std::move(otherInputSpecs)... ) } -{} - - -// ----------------------------------------------------------------------------- -template -auto icarus::ns::util::AssnsCrosser::assPtr - (KeyPtr_t const& keyPtr) const -> TargetPtr_t const& -{ - TargetPtrs_t const& targets = assPtrs(keyPtr); - if (targets.size() > 1) { - // using LogicError because that's what art::FindOne does - throw art::Exception{ art::errors::LogicError } - << "AssnsCrosser::assPtr(): there are " << targets.size() << " " - << lar::debug::demangle() << " objects associated to Ptr<" - << lar::debug::demangle() << ">=" << keyPtr << "!\n"; - } - return targets.empty()? NullTargetPtr: targets.front(); -} // icarus::ns::util::AssnsCrosser::assPtr() - - -// ----------------------------------------------------------------------------- -template -template -auto icarus::ns::util::AssnsCrosser::prepare( - Event const& event, - StartSpecs startSpecs, InputSpecs... otherInputSpecs -) const -> AssnsMap_t -{ - std::optional> keySelector - = keysFromSpecs(event, startSpecs); - HoppingAlgo const algo - = chooseTraversalAlgorithm(startSpecs, otherInputSpecs...); - switch (algo) { - case HoppingAlgo::forward: - return details::MapJoiner::joinForward - (event, std::move(otherInputSpecs)..., keySelector); - case HoppingAlgo::backward: - return details::MapJoiner::joinBackward - (event, std::move(otherInputSpecs)... ); - default: - throw std::logic_error - { "Unexpected direction: " + std::to_string(static_cast(algo)) }; - } // switch -} // icarus::ns::util::AssnsCrosser<>::prepare() - - -// ----------------------------------------------------------------------------- -template -auto icarus::ns::util::AssnsCrosser - ::chooseTraversalAlgorithm -( - StartSpecs const& startSpecs, - InputSpecs const&... otherInputSpecs -) const -> HoppingAlgo { - /* - * If there is a start specification, we need (and can) to go forward. - * Otherwise, we go backward (faster) unless there is no specification for - * the last hop (in which case we can't start from the back). - * - * When both algorithms are available, the backward one is chosen. - */ - - bool const hasStartInfo = startSpecs.hasSpecs(); - - bool const hasEndSpecs - = details::getElement<-1>(otherInputSpecs...).hasSpecs(); - - constexpr std::size_t nHops = sizeof...(OtherTypes); - -#if 0 - // --- BEGIN -- DEBUG -------------------------------------------------------- - // print details about how the specifications are received: - auto const formatter - = [&out=std::cout](auto const& specs){ out << "\n " << specs; }; - std::cout - << "Start specs: " << startSpecs << ", hasStartInfo=" << hasStartInfo - << "\n" << nHops << " hops:"; - (formatter(otherInputSpecs), ...); - std::cout << "\nLast spec: " << details::getElement<-1>(otherInputSpecs...) - << " -> hasEndSpecs=" << hasEndSpecs << std::endl; - - // --- END ---- DEBUG -------------------------------------------------------- -#endif // 0 - if constexpr(nHops == 1) { - if (hasStartInfo) return HoppingAlgo::forward; - if (hasEndSpecs) return HoppingAlgo::backward; - throw std::logic_error - { "Insufficient specifications for single association traversal." }; - } - else { - bool const hasFirstSpecs - = hasStartInfo || details::getElement<0>(otherInputSpecs...).hasSpecs(); - - if (hasFirstSpecs) return HoppingAlgo::forward; - if (hasEndSpecs) return HoppingAlgo::backward; - - throw std::logic_error{ - "Insufficient specifications for traversal of " + std::to_string(nHops) - + " associations." - }; - - } - -} // icarus::ns::util::AssnsCrosser<>::chooseTraversalAlgorithm() - - -// ----------------------------------------------------------------------------- -template -template -auto icarus::ns::util::AssnsCrosser::keysFromSpecs - (Event const& event, StartSpecs const& specs) const - -> std::optional> -{ - /* - * An interface needs to be established. - * The specification may follow the pattern of the InputSpecs, but can't - * use InputSpecsBase as it is now, since the hosted data types may need a - * templated type (see below). - * A list of possible supported input: - * * a product ID: get the handle to the `std::vector` data product and - * the product size (which unfortunately means to read the data product - * itself) and make a list of all pointers - * * a handle or valid handle: - * * an input tag: get the handle of the `std::vector` data product and - * proceed with that as above - * * a product pointer: get the product ID and proceed with that - * * a pointer to the key: require that pointer directly - * * a vector of pointers: require all the pointers in the vector - * - * One can avoid reading the size of the data product, and possibly the data - * product itself, by having a special value that denotes all possible - * pointers from a data product, i.e. from a product ID. - * This special value can be treated either as a variant (e.g. including - * a `art::ProductPtr` and a `art::Ptr`) or assigning a special key to - * an `art::Ptr`, or keeping a separate list of the two types of - * specifications (which is probably the most efficient way). - */ - if (!specs.hasSpecs()) return std::nullopt; - - std::vector> ptrs; - std::vector IDs; - - for (StartSpec const& spec: specs) { - - if (std::holds_alternative(spec)) { - auto const& handle = event.template getValidHandle> - (std::get(spec)); - IDs.push_back(handle.id()); - } - else if (std::holds_alternative>(spec)) { - ptrs.push_back(std::get>(spec)); - } - else if (std::holds_alternative>>(spec)) { - details::append(ptrs, std::get>>(spec)); - } - else if (std::holds_alternative(spec)) { - IDs.push_back(std::get(spec)); - } -#if defined CANVAS_DEC_VERSION && (CANVAS_DEC_VERSION >= 531100) - else if (std::holds_alternative>(spec)) { - IDs.push_back(std::get>(spec).id()); - } -#endif - else if (std::holds_alternative(spec)) { - // ignored, since there are other specs (or `hasSpecs()` would be `false`) - } - else throw art::Exception{ art::errors::LogicError } - << "Start spec holds an unexpected type (" << spec.index() << ").\n"; - - } // for specs - - return std::optional> - { std::in_place, std::move(ptrs), std::move(IDs) }; -} // icarus::ns::util::AssnsCrosser<>::keysFromSpecs() - - -// ----------------------------------------------------------------------------- -template -auto icarus::ns::util::makeAssnsCrosser( - Event const& event, - InputSpecs... inputSpecs -) -> AssnsCrosser -{ - return AssnsCrosser(event, std::move(inputSpecs)...); -} - - -// ----------------------------------------------------------------------------- -template -auto icarus::ns::util::makeAssnsCrosser( - Event const& event, - StartSpecs, - InputSpecs... inputSpecs -) -> AssnsCrosser -{ - return AssnsCrosser(event, std::move(inputSpecs)...); -} - - -// ----------------------------------------------------------------------------- - -#endif // ICARUSALG_UTILITIES_ASSNSCROSSER_H diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e228da7..6535ae6 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,10 +1,3 @@ -cet_make_library(LIBRARY_NAME Test INTERFACE - SOURCE - FrameworkEventMockup.h - LIBRARIES INTERFACE - canvas::canvas -) - # cmake driver file for testing from CET build tools include(CetTest) diff --git a/test/FrameworkEventMockup.h b/test/FrameworkEventMockup.h deleted file mode 100644 index eadb365..0000000 --- a/test/FrameworkEventMockup.h +++ /dev/null @@ -1,580 +0,0 @@ -/** - * @file test/FrameworkEventMockup.h - * @brief Simple _art_-like event mockup. - * @author Gianluca Petrillo (petrillo@slac.stanford.edu) - * @date June 9, 2023 - * - * This library is header only (although there are a couple of inlined things - * that would probably warrant an implementation file). - */ - -#ifndef ICARUSALG_TEST_FRAMEWORKEVENTMOCKUP_H -#define ICARUSALG_TEST_FRAMEWORKEVENTMOCKUP_H - -// framework libraries -#include "canvas/Persistency/Common/Assns.h" -#include "canvas/Persistency/Common/Ptr.h" -#include "canvas/Persistency/Provenance/ProductID.h" -#include "canvas/Persistency/Provenance/BranchDescription.h" -#include "canvas/Persistency/Provenance/ProcessConfiguration.h" -#include "canvas/Persistency/Provenance/TypeLabel.h" -#include "canvas/Utilities/TypeID.h" -#include "canvas/Utilities/InputTag.h" -#include "canvas/Utilities/Exception.h" -#include "cetlib/exempt_ptr.h" -#include "fhiclcpp/ParameterSetID.h" - -// C/C++ standard libraries -#include -#include -#include -#include -#include -#include // std::move() -#include - - -//------------------------------------------------------------------------------ -namespace testing::mockup { - class Event; - template class Handle; - template class ValidHandle; - template class PtrMaker; - namespace details { template class HandleBase; } -} // namespace testing::mockup - -/** - * @brief Mock-up class with a ridiculously small `art::Event`-like interface. - * - * This "event" contains and owns data objects and can return a constant - * reference to them on demand. It is intended to develop unit tests for - * code that requires to read data from an event. - * - * The interface is mimicking _art_'s and _gallery_'s `Event` classes, but it's - * reduced to the very bare minimum. - * - * Supported operations: - * * adding a data product associating it with an input tag (`art::InputTag`); - * the interface of this `put()` is inspired by _art_'s, but does not match - * it (nor it is intended to). In particular, this class does not currently - * use `std::unique_ptr` to store data products. - * * requesting a data product via `art::InputTag`: `getProduct()` mirrors the - * actual `art::Event` interface (it should be also in _gallery_, but as of - * `v1_20_02` that interface has not been added). - * * requesting the product ID (`art::ProductID`) of a data product specified - * by `art::InputTag` with `getProductID()`; this is _very different_ from - * `art::Event::getProductID()`, which returns ID only for data products - * from the current (producer?) module. - * - * Pretty much everything else is _not_ supported, including also: - * * handles - * * product tokens - * * views - * * selectors - * * reading many data products at once - * - * Example: - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} - * testing::mockup::Event fillEvent() { - * testing::mockup::Event event; - * event.put(std::vector{ 0.3, 0.6, 0.9 }, art::InputTag{ "A" }); - * event.put(std::vector{ 1, 6, 5, 9 }, art::InputTag{ "B" }); - * return event; - * } - * - * testing::mockup::Event const event = fillEvent(); - * - * auto const& dataB = event.getProduct>(art::InputTag{ "B" }); - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - */ -class testing::mockup::Event { - - public: - - static std::string const DefaultProcessName; ///< Default process name. - - /** - * @brief Constructor. - * @param processName sets the (default) process name for products without one - */ - Event(std::string processName = DefaultProcessName) - : fProcessName{ std::move(processName) } - {} - - - Event(Event&&) = default; - Event& operator= (Event&&) = default; - - - // --- BEGIN --- Data population interface ----------------------------------- - /// @name Data population interface - /// @{ - - /** - * @brief Moves and registers the specified data under the specified `tag`. - * @param T type of the data product being put into the event - * @param tag the input tag this data product will be registered under - * @param data the content of the data product - * @return the ID of the data product just added - * @throw art::Exception (code: `aer::errors::ProductRegistrationFailure`) - * if a data product with this type and tag is already registered - * - * The `data` is moved into the event and it will be owned by it from now on. - * A product ID is assigned to the data product and returned. - * - * Note that this interface is subtly different from `art::Event`: here we - * must supply a full input `tag`, while _art_ supports just an optional - * instance name; and _art_ (from v. 3.11) returns a handle which is - * convertible to `art::ProductID` instead of the product ID itself. - */ - template - art::ProductID put(T&& data, art::InputTag tag); - - /// @} - // --- END ----- Data population interface ----------------------------------- - - - // --- BEGIN --- Query and retrieval interface ------------------------------- - /// @name Query and retrieval interface - /// @{ - - /// Returns the ID of data product of type `T` and specified input `tag`. - template - art::ProductID getProductID(art::InputTag const& tag) const; - - /// Returns the data product of type `T` and specified input `tag`. - /// @throw art::Exception (code: `art::errors::ProductNotFound`) if not found - template - T const& getProduct(art::InputTag const& tag) const; - - /// Returns a handle to the data product of type `T` and specified `tag`. - template - Handle getHandle(art::InputTag const& tag) const; - - /// Returns a handle to the data product of type `T` and specified `tag`. - /// @throws art::Exception (code: `art::errors::ProductNotFound`) if not found - template - Handle getValidHandle(art::InputTag const& tag) const; - - /** - * @brief Returns the branch description for the specified product ID. - * @param ID the product ID to query about - * @return a branch description object, partially filled - * - * Most of the information in the _art_ branch description either does not - * apply or it is hard to discover in this mockup. - * Currently the only information reliably stored is the input tag. - */ - cet::exempt_ptr getProductDescription - (art::ProductID ID) const; - - - /// @} - // --- END ----- Query and retrieval interface ------------------------------- - - - private: - - struct ProductKey: std::pair { - - using std::pair::pair; - - static int comp(art::InputTag const& a, art::InputTag const& b) noexcept; - - }; // ProductKey - - friend bool operator< (ProductKey const& a, ProductKey const& b) noexcept; - - struct DataProductRecord_t { - art::InputTag tag; - art::ProductID id; - std::any data; - }; - - struct BranchRecord_t { - art::BranchDescription branchDescr; - }; - - - // don't copy (not deleted because we may want in the future a helper to copy) - Event(Event const&) = default; - Event& operator= (Event const&) = default; - - - // --- BEGIN --- Configuration ----------------------------------------------- - - std::string fProcessName; - - // --- END ----- Configuration ----------------------------------------------- - - - // --- BEGIN --- Object data ------------------------------------------------- - - std::size_t fLastProductID = art::ProductID{}.value(); // initialized invalid - - std::map fDataPointers; ///< The data. - - /// Some "branch" information. - std::map fProductIDs; - - // --- END ----- Object data ------------------------------------------------- - - - /// Returns the pointer to the product information for `tag`. - /// @returns pointer to the information record, `nullptr` if not available - template - DataProductRecord_t const* getProductInfo(art::InputTag const& tag) const; - - /// Returns the pointer to the data in the record. Throws if wrong type. - template - T const* getDataPointer(DataProductRecord_t const& dataRecord) const; - - /// Returns the pointer to the product information for `tag`. - /// @throws art::Exception (`art::errors::ProductNotFound`) if not available - template - DataProductRecord_t const& getValidProductInfo - (art::InputTag const& tag) const; - - /// Adds the default process name to the `tag` if it does not have any. - art::InputTag completeTag(art::InputTag tag) const; - - template - static ProductKey makeKey(art::InputTag tag); - -}; // testing::mockup::Event - - -/// Base class for mockup data product handles. -template -class testing::mockup::details::HandleBase { - art::ProductID fID; ///< ID of this product. - T const* fData = nullptr; ///< Pointer to the actual data. - - protected: - void checkValidity() const; - - public: - using element_type = T; - class HandleTag {}; ///< Utility tag to recognise a handle. - - HandleBase() = default; - HandleBase(art::ProductID ID, T const* data): fID{ ID }, fData{ data } {} - - T const& operator*() const { return *product(); } - T const* operator->() const { return product(); } - T const* product() const { return fData; } - - art::ProductID id() const { return fID; } - - explicit operator bool() const noexcept { return isValid(); } - - /// Returns whether the handle has actual data and from a valid source. - bool isValid() const noexcept { return fData && (fID != art::ProductID{}); } - - /// Returns whether the handle has actual data. - bool failedToGet() const { return fData == nullptr; } - -}; // testing::mockup::details::HandleBase - - -/// Mockup class of data product handle. Acts like a "smart" pointer. -template -class testing::mockup::Handle: public details::HandleBase { - using Base_t = details::HandleBase; - - public: - using Base_t::Base_t; - - T const* product() const - { Base_t::checkValidity(); return Base_t::product(); } - -}; // testing::mockup::Handle - - -/// Mockup class of data product valid handle. Acts like a "smart" pointer. -template -class testing::mockup::ValidHandle: public details::HandleBase { - using Base_t = details::HandleBase; - - public: - ValidHandle(art::ProductID ID, T const* data): Base_t{ ID, data } {} - -}; // testing::mockup::ValidHandle - - -// ----------------------------------------------------------------------------- -/** - * @brief Creates `art::Ptr` from the specified data product. - * @tparam T data type of the pointers - * - * This class is initialised with a data product (either product ID and data, - * or event and input tag) of type `std::vector` and can return functional - * `art::Ptr` to the elements of that data product. - * - * Example: - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} - * testing::mockup::Event event; - * event.put(std::vector{ 0.3, 0.6, 0.9 }, art::InputTag{ "A" }); - * event.put(std::vector{ 1, 6, 5, 9 }, art::InputTag{ "B" }); - * - * auto const& dataB = event.getProduct>(art::InputTag{ "B" }); - * - * testing::mockup::PtrMaker const makeAptr{ event, art::InputTag{ "A" } }; - * testing::mockup::PtrMaker const makeBptr{ event, art::InputTag{ "B" } }; - * - * art::Assns assnsAB; - * assnsAB.addSingle(makeAptr(1), makeBptr(1)); - * assnsAB.addSingle(makeAptr(2), makeBptr(3)); - * event.put(std::move(assnsAB, art::InputTag{ "B" }); - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - */ -template -class testing::mockup::PtrMaker { - - public: - using Data_t = T; - using ProdColl_t = std::vector; - using Ptr_t = art::Ptr; - - /** - * @brief Constructor: pointers to a product of specified `tag` from `event`. - * @param event the "event" to read the data product from - * @param tag the input tag of the data product - * - * The data product with the specified `tag` is read from `event`, and an - * `art::Ptr` is created out of it. - * The pointer can actually dereference to the data. - * - * This interface is not compatible with `art::PtrMaker`. - * - * The data product must exist in `event`. - */ - PtrMaker(Event const& event, art::InputTag const& tag) - : PtrMaker - { event.getProductID(tag), event.getProduct(tag) } - {} - - /** - * @brief Constructor: pointers with product `prodID` and pointing to `data`. - * @param prodID product ID to be assigned to the pointers - * @param data data the pointers will be pointing to - * - * Pointers will have the specified product ID and will point to elements of - * the `data` collection. - * The pointer can actually dereference to the data. - * Pointers may becoming dangling if the underlying content of `data` is - * deleted or moved away (which is normally not possible in `art::Event`). - * They may also point to non-existing elements after the end of the data - * collection, condition which is not checked. - * - * This interface is not compatible with `art::PtrMaker`. - */ - PtrMaker(art::ProductID prodID, ProdColl_t const& data) - : fProdID{ prodID }, fData{ data } {} - - // @{ - /** - * @brief Creates a pointer to the specified element of the data product. - * @param index the index of the element in the data product - * @return a pointer - * - * No check is performed on the index, which may point beyond the end of the - * data product. - */ - Ptr_t make(std::size_t index) const - { return Ptr_t{ fProdID, &(fData[index]), index }; } - Ptr_t operator() (std::size_t index) const { return make(index); } - // @} - - private: - - art::ProductID const fProdID; ///< Product ID to record in the pointers. - ProdColl_t const& fData; ///< Pointer to the original data product. - -}; // testing::mockup::PtrMaker - - -// ----------------------------------------------------------------------------- -// --- template implementation -// ----------------------------------------------------------------------------- -// --- testing::mockup::Event -// ----------------------------------------------------------------------------- -inline std::string const testing::mockup::Event::DefaultProcessName{ "mockup" }; - - -// ----------------------------------------------------------------------------- -int testing::mockup::Event::ProductKey::comp - (art::InputTag const& a, art::InputTag const& b) noexcept -{ - if (int cmp = a.process().compare(b.process())) return cmp; - if (int cmp = a.label().compare(b.label())) return cmp; - return a.instance().compare(b.instance()); -} - - -// ----------------------------------------------------------------------------- -namespace testing::mockup { - - bool operator< - (Event::ProductKey const& a, Event::ProductKey const& b) noexcept - { - if (int cmp = Event::ProductKey::comp(a.first, b.first)) return cmp < 0; - return a.second < b.second; - } // operator< (Event::ProductKey, Event::ProductKey) - -} // namespace testing::mockup - - -// ----------------------------------------------------------------------------- -template -art::ProductID testing::mockup::Event::put(T&& data, art::InputTag tag) { - - tag = completeTag(tag); - auto key = makeKey(tag); - if (fDataPointers.find(key) != fDataPointers.end()) { - throw art::Exception{ art::errors::ProductRegistrationFailure } - << "Data product '" << tag.encode() << "' already registered.\n"; - } - - art::ProductID ID{ ++fLastProductID }; - fhicl::ParameterSetID const PSetID{}; // no parameter set ID - - fProductIDs.emplace( - ID, - BranchRecord_t{ - art::BranchDescription{ - art::InEvent // branch type - , art::TypeLabel{ art::TypeID{ typeid(T) }, tag.instance(), true } - , tag.label() - , PSetID - , art::ProcessConfiguration{ tag.process(), PSetID, "" } - } - } - ); - - fDataPointers.emplace( - std::move(key), - DataProductRecord_t{ - std::move(tag), - ID, - std::move(data) - } - ); - - return ID; -} // testing::mockup::Event::put() - - -// ----------------------------------------------------------------------------- -template -art::ProductID testing::mockup::Event::getProductID - (art::InputTag const& tag) const - { return getValidProductInfo(tag).id; } - - -// ----------------------------------------------------------------------------- -template -T const& testing::mockup::Event::getProduct(art::InputTag const& tag) const { - return *getDataPointer(getValidProductInfo(tag)); -} // testing::mockup::Event::getProduct() - - -// ----------------------------------------------------------------------------- -template -auto testing::mockup::Event::getHandle(art::InputTag const& tag) const - -> Handle -{ - if (DataProductRecord_t const* dataRecord = getProductInfo(tag)) - return { dataRecord->id, getDataPointer(*dataRecord) }; - return {}; -} // testing::mockup::Event::getHandle() - - -// ----------------------------------------------------------------------------- -template -auto testing::mockup::Event::getValidHandle(art::InputTag const& tag) const - -> Handle -{ - DataProductRecord_t const& dataRecord = getValidProductInfo(tag); - return { dataRecord.id, getDataPointer(dataRecord) }; -} // testing::mockup::Event::getValidHandle() - - -// ----------------------------------------------------------------------------- -template -auto testing::mockup::Event::getProductInfo(art::InputTag const& tag) const - -> DataProductRecord_t const* -{ - auto const it = fDataPointers.find(makeKey(completeTag(tag))); - return (it == fDataPointers.end())? nullptr: &(it->second); -} // testing::mockup::Event::getProductInfo() - - -// ----------------------------------------------------------------------------- -template -auto testing::mockup::Event::getValidProductInfo(art::InputTag const& tag) const - -> DataProductRecord_t const& -{ - DataProductRecord_t const* dataRecord = getProductInfo(tag); - if (dataRecord) return *dataRecord; - throw art::Exception{ art::errors::ProductNotFound } - << "Data product '" << tag.encode() << "' not registered or wrong type.\n"; -} // testing::mockup::Event::getValidProductInfo() - - -// ----------------------------------------------------------------------------- -template -T const* testing::mockup::Event::getDataPointer - (DataProductRecord_t const& dataRecord) const -{ - try { return &std::any_cast(dataRecord.data); } - catch (std::bad_any_cast const&) { - throw art::Exception{ art::errors::LogicError } - << "Data product '" << dataRecord.tag.encode() - << "' not of requested type.\n"; - } -} // testing::mockup::Event::getDataPointer() - - -// ----------------------------------------------------------------------------- -cet::exempt_ptr -inline testing::mockup::Event::getProductDescription - (art::ProductID ID) const -{ - auto const it = fProductIDs.find(ID); - return (it == fProductIDs.end())? nullptr: &it->second.branchDescr; -} - - -// ----------------------------------------------------------------------------- -template -auto testing::mockup::Event::makeKey(art::InputTag tag) -> ProductKey - { return { std::move(tag), std::type_index{ typeid(T) } }; } - - -// ----------------------------------------------------------------------------- -inline art::InputTag testing::mockup::Event::completeTag - (art::InputTag tag) const -{ - if (tag.process().empty()) - return art::InputTag{ tag.label(), tag.instance(), fProcessName }; - else return tag; -} // art::InputTag testing::mockup::Event::completeTag() - - -// ----------------------------------------------------------------------------- -// --- testing::mockup::Handle and related -// ----------------------------------------------------------------------------- -template -void testing::mockup::details::HandleBase::checkValidity() const { - if (fData) return; - throw art::Exception(art::errors::NullPointerError) - << "Attempt to de-reference product that points to 'nullptr'.\n"; -} - - -// ----------------------------------------------------------------------------- - -#endif // ICARUSALG_TEST_FRAMEWORKEVENTMOCKUP_H diff --git a/test/Utilities/AssnsCrosser_test.cc b/test/Utilities/AssnsCrosser_test.cc deleted file mode 100644 index 3c02fb3..0000000 --- a/test/Utilities/AssnsCrosser_test.cc +++ /dev/null @@ -1,1114 +0,0 @@ -/** - * @file AssnsCrosser_test.cc - * @brief Unit test for `icarus::ns::util::AssnsCrosser` class and utilities. - * @author Gianluca Petrillo (petrillo@slac.stanford.edu) - * @date June 9, 2023 - * @see icarusalg/Utilities/AssnsCrosser.h - */ - - -// Boost libraries -#define BOOST_TEST_MODULE AssnsCrosser -#include // BOOST_AUTO_TEST_CASE() -#include // BOOST_TEST() - -// library to test -#include "icarusalg/Utilities/AssnsCrosser.h" - -// ICARUS and LArSoft libraries -#include "test/FrameworkEventMockup.h" -#include "larcorealg/CoreUtils/enumerate.h" - -// C/C++ standard libraries -#include -#include -#include -#include // std::runtime_error -#include -#include -#include // std::move() -#include - - -//------------------------------------------------------------------------------ -// test data -template -struct DataType { - - static constexpr std::size_t tag = Tag; - - static constexpr std::size_t NoID = 0; - - std::size_t ID = NoID; - - DataType(std::size_t ID = NoID): ID(ID) {} - - operator std::string() const - { - return - "DataType<" + std::to_string(tag) + ">[ID=" + std::to_string(ID) + "]"; - } - -}; // DataType<> - - -struct DataTypeA: DataType<1> { using DataType<1>::DataType; }; -struct DataTypeB: DataType<2> { using DataType<2>::DataType; }; -struct DataTypeC: DataType<3> { using DataType<3>::DataType; }; -struct DataTypeD: DataType<4> { using DataType<4>::DataType; }; -struct DataTypeE: DataType<5> { using DataType<5>::DataType; }; -struct DataTypeF: DataType<6> { using DataType<6>::DataType; }; -struct DataTypeG: DataType<7> { using DataType<7>::DataType; }; - - -//------------------------------------------------------------------------------ -testing::mockup::Event makeTestEvent1() { - std::vector dataA { // 16 - /* 0 */ DataTypeA{ 0 }, - /* 1 */ DataTypeA{ 16 }, - /* 2 */ DataTypeA{ 32 }, - /* 3 */ DataTypeA{ 48 }, - /* 4 */ DataTypeA{ 64 } - }; - std::vector dataA1 { // 16 - /* 0 */ DataTypeA{ 0 }, - /* 1 */ DataTypeA{ 16 }, - }; - std::vector dataA2 { // 16 - /* 0 */ DataTypeA{ 32 }, - /* 1 */ DataTypeA{ 48 }, - /* 2 */ DataTypeA{ 64 } - }; - - std::vector dataB { // 8 - /* 0 */ DataTypeB{ 16 }, - /* 1 */ DataTypeB{ 24 }, - /* 2 */ DataTypeB{ 32 }, - /* 3 */ DataTypeB{ 48 }, - /* 4 */ DataTypeB{ 56 } - }; - - std::vector dataC { // 4 - /* 0 */ DataTypeC{ 16 }, - /* 1 */ DataTypeC{ 20 }, - /* 2 */ DataTypeC{ 24 }, - /* 3 */ DataTypeC{ 28 }, - /* 4 */ DataTypeC{ 32 }, - /* 5 */ DataTypeC{ 56 }, - /* 6 */ DataTypeC{ 60 }, - /* 7 */ DataTypeC{ 64 }, - /* 8 */ DataTypeC{ 72 } - }; - - std::vector dataD { // 2 - /* 0 */ DataTypeD{ 16 }, - /* 1 */ DataTypeD{ 18 }, - /* 2 */ DataTypeD{ 28 }, - /* 3 */ DataTypeD{ 36 }, - /* 4 */ DataTypeD{ 60 }, - /* 5 */ DataTypeD{ 64 }, - /* 6 */ DataTypeD{ 72 }, - /* 7 */ DataTypeD{ 76 }, - }; - - testing::mockup::Event event; - - event.put(std::move(dataA), art::InputTag{ "A" }); - event.put(std::move(dataA1), art::InputTag{ "A1" }); - event.put(std::move(dataA2), art::InputTag{ "A2" }); - event.put(std::move(dataB), art::InputTag{ "B" }); - event.put(std::move(dataC), art::InputTag{ "C" }); - event.put(std::move(dataD), art::InputTag{ "D" }); - - testing::mockup::PtrMaker makeAptr{ event, art::InputTag{ "A" } }; - testing::mockup::PtrMaker makeA1ptr - { event, art::InputTag{ "A1" } }; - testing::mockup::PtrMaker makeA2ptr - { event, art::InputTag{ "A2" } }; - testing::mockup::PtrMaker makeBptr{ event, art::InputTag{ "B" } }; - testing::mockup::PtrMaker makeCptr{ event, art::InputTag{ "C" } }; - testing::mockup::PtrMaker makeDptr{ event, art::InputTag{ "D" } }; - - /* - * The plan: - * A[0] <=> none - * A[1] <=> B[0], B[1] - * A[2] <=> B[2] - * A[3] <=> B[3] - * A[4] <=> none - * none <=> B[4] - * - * B[0] <=> C[0], C[1] - * B[1] <=> C[2], C[3] - * B[2] <=> C[4] - * B[3] <=> none - * B[4] <=> C[5], C[6] - * none <=> C[7] - * none <=> C[8] - * - * C[0] <=> D[0], D[1] - * C[1] <=> none - * C[2] <=> none - * C[3] <=> D[2] - * none <=> D[3] - * C[4] <=> none - * C[5] <=> none - * C[6] <=> D[4] - * C[7] <=> D[5] - * C[8] <=> D[6] - * none <=> D[7] - * - * A1[0] <=> none - * A1[1] <=> B[0], B[1] - * none <=> B[2] - * none <=> B[3] - * none <=> B[4] - * - * none <=> B[0], B[1] - * A2[0] <=> B[2] - * A2[1] <=> B[3] - * A2[2] <=> none - * none <=> B[4] - * - * - * A[0] <=> none <=> none <=> none - * A[1] <=> B[0..1] <=> C[0..3] <=> D[0..2] - * A[2] <=> B[2] <=> C[4] <=> none - * A[3] <=> B[3] <=> none <=> none - * A[4] <=> none <=> none <=> none - * - * A1[0] <=> none <=> none <=> none - * A1[1] <=> B[0..1] <=> C[0..3] <=> D[0..2] - * A2[0] <=> B[2] <=> C[4] <=> none - * A2[1] <=> B[3] <=> none <=> none - * A2[2] <=> none <=> none <=> none - * - */ - art::Assns assnsAB; - assnsAB.addSingle(makeAptr(1), makeBptr(0)); - assnsAB.addSingle(makeAptr(1), makeBptr(1)); - assnsAB.addSingle(makeAptr(2), makeBptr(2)); - assnsAB.addSingle(makeAptr(3), makeBptr(3)); - event.put(std::move(assnsAB), art::InputTag{ "B" }); - - art::Assns assnsA1B; - assnsA1B.addSingle(makeA1ptr(1), makeBptr(0)); - assnsA1B.addSingle(makeA1ptr(1), makeBptr(1)); - event.put(std::move(assnsA1B), art::InputTag{ "B:1" }); - - art::Assns assnsA2B; - assnsA2B.addSingle(makeA2ptr(0), makeBptr(2)); - assnsA2B.addSingle(makeA2ptr(1), makeBptr(3)); - event.put(std::move(assnsA2B), art::InputTag{ "B:2" }); - - art::Assns assnsBC; - assnsBC.addSingle(makeBptr(0), makeCptr(0)); - assnsBC.addSingle(makeBptr(0), makeCptr(1)); - assnsBC.addSingle(makeBptr(1), makeCptr(2)); - assnsBC.addSingle(makeBptr(1), makeCptr(3)); - assnsBC.addSingle(makeBptr(2), makeCptr(4)); - assnsBC.addSingle(makeBptr(4), makeCptr(5)); - assnsBC.addSingle(makeBptr(4), makeCptr(6)); - event.put(std::move(assnsBC), art::InputTag{ "C" }); - - art::Assns assnsCD; - assnsCD.addSingle(makeCptr(0), makeDptr(0)); - assnsCD.addSingle(makeCptr(0), makeDptr(1)); - assnsCD.addSingle(makeCptr(3), makeDptr(2)); - assnsCD.addSingle(makeCptr(6), makeDptr(4)); - assnsCD.addSingle(makeCptr(7), makeDptr(5)); - assnsCD.addSingle(makeCptr(8), makeDptr(6)); - event.put(std::move(assnsCD), art::InputTag{ "D" }); - - return event; -} // makeTestEvent1() - - -// ----------------------------------------------------------------------------- -void AssnsCrosser1_test() { - /* - * Test with a single association. - * - * The plan: - * A[0] <=> none - * A[1] <=> B[0], B[1] - * A[2] <=> B[2] - * A[3] <=> B[3] - * A[4] <=> none - * none <=> B[4] - */ - - testing::mockup::Event const event = makeTestEvent1(); - - testing::mockup::PtrMaker makeAptr{ event, art::InputTag{ "A" } }; - testing::mockup::PtrMaker makeBptr{ event, art::InputTag{ "B" } }; - - icarus::ns::util::AssnsCrosser AtoB - { event, art::InputTag{ "B" } }; - - { - auto const& Bs = AtoB.assPtrs(makeAptr(0)); - static_assert - (std::is_same_v> const&>); - - BOOST_TEST(Bs.empty()); - } - - { - auto const& Bs = AtoB.assPtrs(makeAptr(1)); - - BOOST_TEST(Bs.size() == 2); - if (Bs.size() > 0) BOOST_TEST(Bs[0] == makeBptr(0)); - if (Bs.size() > 1) BOOST_TEST(Bs[1] == makeBptr(1)); - } - - { - auto const& Bs = AtoB.assPtrs(makeAptr(2)); - - BOOST_TEST(Bs.size() == 1); - if (Bs.size() > 0) BOOST_TEST(Bs[0] == makeBptr(2)); - } - - { - auto const& Bs = AtoB.assPtrs(makeAptr(3)); - - BOOST_TEST(Bs.size() == 1); - if (Bs.size() > 0) BOOST_TEST(Bs[0] == makeBptr(3)); - } - - { - auto const& Bs = AtoB.assPtrs(makeAptr(4)); - - BOOST_TEST(Bs.empty()); - } - - { - auto const& Bs = AtoB.assPtrs(makeAptr(5)); - - BOOST_TEST(Bs.empty()); - } - - { - auto const& Bs = AtoB.assPtrs(makeAptr(6)); - static_assert - (std::is_same_v> const&>); - BOOST_TEST(Bs.empty()); - } - -} // AssnsCrosser1_test() - - -//------------------------------------------------------------------------------ -void AssnsCrosser2check( - testing::mockup::Event const& event, - icarus::ns::util::AssnsCrosser const& AtoC -) { - /* - * Test with a three-hop association. - * - * The plan: - * A[0] <=> none - * A[1] <=> B[0], B[1] - * A[2] <=> B[2] - * A[3] <=> B[3] - * A[4] <=> none - * none <=> B[4] - * - * B[0] <=> C[0], C[1] - * B[1] <=> C[2], C[3] - * B[2] <=> C[4] - * B[3] <=> none - * B[4] <=> C[5], C[6] - * none <=> C[7] - * none <=> C[8] - * - * A[0] <=> none <=> none <=> none - * A[1] <=> B[0..1] <=> C[0..3] <=> D[0..2] - * A[2] <=> B[2] <=> C[4] <=> none - * A[3] <=> B[3] <=> none <=> none - * A[4] <=> none <=> none <=> none - * - */ - - testing::mockup::PtrMaker makeAptr{ event, art::InputTag{ "A" } }; - testing::mockup::PtrMaker makeCptr{ event, art::InputTag{ "C" } }; - - { - auto const& Cs = AtoC.assPtrs(makeAptr(0)); - static_assert - (std::is_same_v> const&>); - - BOOST_TEST(Cs.empty()); - } - - { - auto const& Cs = AtoC.assPtrs(makeAptr(1)); - - BOOST_TEST(Cs.size() == 4); - if (Cs.size() > 0) BOOST_TEST(Cs[0] == makeCptr(0)); - if (Cs.size() > 1) BOOST_TEST(Cs[1] == makeCptr(1)); - if (Cs.size() > 2) BOOST_TEST(Cs[2] == makeCptr(2)); - if (Cs.size() > 3) BOOST_TEST(Cs[3] == makeCptr(3)); - } - - { - auto const& Cs = AtoC.assPtrs(makeAptr(2)); - - BOOST_TEST(Cs.size() == 1); - if (Cs.size() > 0) BOOST_TEST(Cs[0] == makeCptr(4)); - } - - { - auto const& Cs = AtoC.assPtrs(makeAptr(3)); - - BOOST_TEST(Cs.empty()); - } - - { - auto const& Cs = AtoC.assPtrs(makeAptr(4)); - - BOOST_TEST(Cs.empty()); - } - - { - auto const& Cs = AtoC.assPtrs(makeAptr(5)); - static_assert - (std::is_same_v> const&>); - BOOST_TEST(Cs.empty()); - } - -} // AssnsCrosser2check() - - -//------------------------------------------------------------------------------ -void AssnsCrosser2_test() { - /* - * Test with a two-hop association. - * - * The plan: - * A[0] <=> none - * A[1] <=> B[0], B[1] - * A[2] <=> B[2] - * A[3] <=> B[3] - * A[4] <=> none - * none <=> B[4] - * - * B[0] <=> C[0], C[1] - * B[1] <=> C[2], C[3] - * B[2] <=> C[4] - * B[3] <=> none - * B[4] <=> C[5], C[6] - * none <=> C[7] - * none <=> C[8] - * - * A[0] <=> none - * A[1] <=> B[0..1] <=> C[0..3] - * A[2] <=> B[2] <=> C[4] - * A[3] <=> B[3] <=> none - * A[4] <=> none <=> none - * - */ - - testing::mockup::Event const event = makeTestEvent1(); - - icarus::ns::util::AssnsCrosser AtoC - { event, art::InputTag{ "B" }, art::InputTag{ "C" } }; - - BOOST_TEST_CONTEXT("Test: 2 hops with full InputTag specification") { - AssnsCrosser2check(event, AtoC); - } - -} // AssnsCrosser2_test() - - -//------------------------------------------------------------------------------ -void AssnsCrosserDiamond_test() { - /* - * Test with a diamond association. - */ - - std::vector dataA { DataTypeA{ 10 } }; - std::vector dataB { DataTypeB{ 20 }, DataTypeB{ 21 } }; - std::vector dataC { DataTypeC{ 30 } }; - - testing::mockup::Event event; - - event.put(std::move(dataA), art::InputTag{ "A" }); - event.put(std::move(dataB), art::InputTag{ "B" }); - event.put(std::move(dataC), art::InputTag{ "C" }); - - testing::mockup::PtrMaker makeAptr{ event, art::InputTag{ "A" } }; - testing::mockup::PtrMaker makeBptr{ event, art::InputTag{ "B" } }; - testing::mockup::PtrMaker makeCptr{ event, art::InputTag{ "C" } }; - - /* - * The plan: - * A[1] <=> B[0], B[1] - * - * B[0] <=> C[0], - * B[1] <=> C[0] - * - * A[0] <=> B[0..1] <=> C[0] (but via two paths) - */ - art::Assns assnsAB; - assnsAB.addSingle(makeAptr(0), makeBptr(0)); - assnsAB.addSingle(makeAptr(0), makeBptr(1)); - event.put(std::move(assnsAB), art::InputTag{ "B" }); - - art::Assns assnsBC; - assnsBC.addSingle(makeBptr(0), makeCptr(0)); - assnsBC.addSingle(makeBptr(1), makeCptr(0)); - event.put(std::move(assnsBC), art::InputTag{ "C" }); - - using icarus::ns::util::hopTo; - - auto const AtoC = icarus::ns::util::makeAssnsCrosser - (event, hopTo("B"), hopTo("C")); - - auto const& Cs = AtoC.assPtrs(makeAptr(0)); - - BOOST_TEST(Cs.size() == 2); - if (Cs.size() > 0) BOOST_TEST(Cs[0] == makeCptr(0)); - if (Cs.size() > 1) BOOST_TEST(Cs[1] == makeCptr(0)); - -} // AssnsCrosserDiamond_test() - - -//------------------------------------------------------------------------------ -void AssnsCrosser3check( - testing::mockup::Event const& event, - icarus::ns::util::AssnsCrosser const& AtoD -) { - /* - * Test with a three-hop association. - * - * The plan: - * A[0] <=> none - * A[1] <=> B[0], B[1] - * A[2] <=> B[2] - * A[3] <=> B[3] - * A[4] <=> none - * none <=> B[4] - * - * B[0] <=> C[0], C[1] - * B[1] <=> C[2], C[3] - * B[2] <=> C[4] - * B[3] <=> none - * B[4] <=> C[5], C[6] - * none <=> C[7] - * none <=> C[8] - * - * C[0] <=> D[0], D[1] - * C[1] <=> none - * C[2] <=> none - * C[3] <=> D[2] - * none <=> D[3] - * C[4] <=> none - * C[5] <=> none - * C[6] <=> D[4] - * C[7] <=> D[5] - * C[8] <=> D[6] - * none <=> D[7] - * - * A[0] <=> none <=> none <=> none - * A[1] <=> B[0..1] <=> C[0..3] <=> D[0..2] - * A[2] <=> B[2] <=> C[4] <=> none - * A[3] <=> B[3] <=> none <=> none - * A[4] <=> none <=> none <=> none - * - */ - - testing::mockup::PtrMaker makeAptr{ event, art::InputTag{ "A" } }; - testing::mockup::PtrMaker makeDptr{ event, art::InputTag{ "D" } }; - - { - auto const& Ds = AtoD.assPtrs(makeAptr(0)); - static_assert - (std::is_same_v> const&>); - - BOOST_TEST(Ds.empty()); - } - - { - auto const& Ds = AtoD.assPtrs(makeAptr(1)); - - BOOST_TEST(Ds.size() == 3); - if (Ds.size() > 0) BOOST_TEST(Ds[0] == makeDptr(0)); - if (Ds.size() > 1) BOOST_TEST(Ds[1] == makeDptr(1)); - if (Ds.size() > 2) BOOST_TEST(Ds[2] == makeDptr(2)); - } - - { - auto const& Ds = AtoD.assPtrs(makeAptr(2)); - BOOST_TEST(Ds.empty()); - } - - { - auto const& Ds = AtoD.assPtrs(makeAptr(3)); - BOOST_TEST(Ds.empty()); - } - - { - auto const& Ds = AtoD.assPtrs(makeAptr(4)); - BOOST_TEST(Ds.empty()); - } - - { - auto const& Ds = AtoD.assPtrs(makeAptr(5)); - static_assert - (std::is_same_v> const&>); - BOOST_TEST(Ds.empty()); - } - -} // AssnsCrosser3check() - - -//------------------------------------------------------------------------------ -void AssnsCrosser3_test() { - - testing::mockup::Event const event = makeTestEvent1(); - - icarus::ns::util::AssnsCrosser - const AtoD - { event, art::InputTag{ "B" }, art::InputTag{ "C" }, art::InputTag{ "D" } }; - - AssnsCrosser3check(event, AtoD); - -} // AssnsCrosser3_test() - - -//------------------------------------------------------------------------------ -void AssnsCrosser3withID_test() { - - testing::mockup::Event const event = makeTestEvent1(); - - // the tag of B <=> C associations is the same as the one of the C data; - // we have lost track of the ID of the latter, but we can still ask the event - art::ProductID const dataC_ID - = event.getProductID>("C"); - BOOST_TEST_REQUIRE(dataC_ID != art::ProductID{}); - - icarus::ns::util::AssnsCrosser - const AtoD - { event, art::InputTag{ "B" }, dataC_ID, art::InputTag{ "D" } }; - - BOOST_TEST_CONTEXT("Test: 3 hops with a product ID") { - AssnsCrosser3check(event, AtoD); - } - -} // AssnsCrosser3withID_test() - - -//------------------------------------------------------------------------------ -void AssnsCrosser3withJump_test() { - /* - * In this test, the first hop should be discovered. - * - * The selected algorithm should be the backward one - * (because the forward one has no starting point) - * and the "D" associations should point to "C" data, - * the (implicitly converted) "C" input tag should point to "B" data, - * and "B" tag should also denote an association to "A". - * The tag "B" should be discovered from the left pointers of the "C" - * association. - */ - - testing::mockup::Event const event = makeTestEvent1(); - - icarus::ns::util::AssnsCrosser - const AtoD - { event, {}, "C", art::InputTag{ "D" } }; - - BOOST_TEST_CONTEXT("Test: 3 hops with autodetection of first hop") { - AssnsCrosser3check(event, AtoD); - } - -} // AssnsCrosser3withJump_test() - - -//------------------------------------------------------------------------------ -void AssnsCrosser3with2jumps_test() { - /* - * In this test, the first and second hops should be discovered. - * - * The selected algorithm should be the backward one - * (because the forward one has no starting point) - * and the "D" associations should point to "C" data, - * and "C" tag should also denote an association to "B". - * The tag "C" should be discovered from the left pointers of the "D" - * association. - * The same should afterward happen from "C" to "B". - */ - - testing::mockup::Event const event = makeTestEvent1(); - - icarus::ns::util::AssnsCrosser - const AtoD - { event, {}, {}, "D" }; - - BOOST_TEST_CONTEXT("Test: 3 hops with autodetection of first and second hop") - { - AssnsCrosser3check(event, AtoD); - } - -} // AssnsCrosser3with2jumps_test() - - -//------------------------------------------------------------------------------ -void AssnsCrosserInputList1_test() { - /* - * Test with a two-hop association. - * - * The plan: - * A1[0] <=> none - * A1[1] <=> B[0], B[1] - * A2[0] <=> B[2] - * A2[1] <=> B[3] - * A2[2] <=> none - * none <=> B[4] - * - * B[0] <=> C[0], C[1] - * B[1] <=> C[2], C[3] - * B[2] <=> C[4] - * B[3] <=> none - * B[4] <=> C[5], C[6] - * none <=> C[7] - * none <=> C[8] - * - * A1[0] <=> none - * A1[1] <=> B[0..1] <=> C[0..3] - * A2[0] <=> B[2] <=> C[4] - * A2[1] <=> B[3] <=> none - * A2[2] <=> none <=> none - * - */ - testing::mockup::Event event = makeTestEvent1(); - - testing::mockup::PtrMaker makeA1ptr - { event, art::InputTag{ "A1" } }; - testing::mockup::PtrMaker makeA2ptr - { event, art::InputTag{ "A2" } }; - testing::mockup::PtrMaker makeCptr{ event, art::InputTag{ "C" } }; - - // note that the associations between A1/2 and B are called B:1 and B:2 - using icarus::ns::util::hopTo; - auto const AtoC = icarus::ns::util::makeAssnsCrosser( - event, - hopTo{ "B:1", "B:2" }, hopTo{ "C" } - ); - - { - auto const& Cs = AtoC.assPtrs(makeA1ptr(0)); - static_assert - (std::is_same_v> const&>); - - BOOST_TEST(Cs.empty()); - } - - { - auto const& Cs = AtoC.assPtrs(makeA1ptr(1)); - - BOOST_TEST(Cs.size() == 4); - if (Cs.size() > 0) BOOST_TEST(Cs[0] == makeCptr(0)); - if (Cs.size() > 1) BOOST_TEST(Cs[1] == makeCptr(1)); - if (Cs.size() > 2) BOOST_TEST(Cs[2] == makeCptr(2)); - if (Cs.size() > 3) BOOST_TEST(Cs[3] == makeCptr(3)); - } - - { - auto const& Cs = AtoC.assPtrs(makeA1ptr(2)); - static_assert - (std::is_same_v> const&>); - BOOST_TEST(Cs.empty()); - } - - { - auto const& Cs = AtoC.assPtrs(makeA2ptr(0)); - - BOOST_TEST(Cs.size() == 1); - if (Cs.size() > 0) BOOST_TEST(Cs[0] == makeCptr(4)); - } - - { - auto const& Cs = AtoC.assPtrs(makeA2ptr(1)); - - BOOST_TEST(Cs.empty()); - } - - { - auto const& Cs = AtoC.assPtrs(makeA2ptr(2)); - - BOOST_TEST(Cs.empty()); - } - - { - auto const& Cs = AtoC.assPtrs(makeA2ptr(3)); - static_assert - (std::is_same_v> const&>); - BOOST_TEST(Cs.empty()); - } - -} // AssnsCrosserInputList1_test() - - -//------------------------------------------------------------------------------ -void AssnsCrosserStartList1_test() { - - using icarus::ns::util::startFrom; - - testing::mockup::Event const event = makeTestEvent1(); - - /* - // the tag of B <=> C associations is the same as the one of the C data; - // we have lost track of the ID of the latter, but we can still ask the event - art::ProductID const dataC_ID - = event.getProductID>("C"); - BOOST_TEST_REQUIRE(dataC_ID != art::ProductID{}); - */ - - testing::mockup::PtrMaker makeAptr{ event, art::InputTag{ "A" } }; - testing::mockup::PtrMaker makeCptr{ event, art::InputTag{ "C" } }; - - icarus::ns::util::AssnsCrosser const AtoC - { event, { makeAptr(2), makeAptr(3) }, "B", "C" }; - - { - auto const& Cs = AtoC.assPtrs(makeAptr(0)); - static_assert - (std::is_same_v> const&>); - - BOOST_TEST(Cs.empty()); - } - - { - auto const& Cs = AtoC.assPtrs(makeAptr(1)); - - BOOST_TEST(Cs.empty()); - } - - { - auto const& Cs = AtoC.assPtrs(makeAptr(2)); - - BOOST_TEST(Cs.size() == 1); - if (Cs.size() > 0) BOOST_TEST(Cs[0] == makeCptr(4)); - } - - { - auto const& Cs = AtoC.assPtrs(makeAptr(3)); - - BOOST_TEST(Cs.empty()); - } - - { - auto const& Cs = AtoC.assPtrs(makeAptr(4)); - - BOOST_TEST(Cs.empty()); - } - - { - auto const& Cs = AtoC.assPtrs(makeAptr(5)); - static_assert - (std::is_same_v> const&>); - BOOST_TEST(Cs.empty()); - } - -} // AssnsCrosserStartList1_test() - - -//------------------------------------------------------------------------------ -void AssnsCrosserStartList2_test() { - - using icarus::ns::util::startFrom; - - testing::mockup::Event const event = makeTestEvent1(); - - art::ProductID const dataA_ID - = event.getProductID>("A"); - BOOST_TEST_REQUIRE(dataA_ID != art::ProductID{}); - - icarus::ns::util::AssnsCrosser const AtoC - { event, dataA_ID, "B", "C" }; - - BOOST_TEST_CONTEXT("Test: 2 hops with a product ID") { - AssnsCrosser2check(event, AtoC); - } - -} // AssnsCrosserStartList2_test() - - -//------------------------------------------------------------------------------ -void AssnsCrosserStartList3_test() { - - using icarus::ns::util::startFrom; - - testing::mockup::Event const event = makeTestEvent1(); - - // startFrom{} is not required, but it increases readability - icarus::ns::util::AssnsCrosser const AtoC - { event, startFrom{ "A" }, "B", "C" }; - - BOOST_TEST_CONTEXT("Test: 2 hops with an input tag start") { - AssnsCrosser2check(event, AtoC); - } - -} // AssnsCrosserStartList3_test() - - -//------------------------------------------------------------------------------ -void AssnsCrosserStartList4_test() { - - using icarus::ns::util::startFrom; - - testing::mockup::Event const event = makeTestEvent1(); - - testing::mockup::PtrMaker makeAptr{ event, art::InputTag{ "A" } }; - testing::mockup::PtrMaker makeCptr{ event, art::InputTag{ "C" } }; - - std::vector const startA{ makeAptr(2), makeAptr(3) }; - icarus::ns::util::AssnsCrosser const AtoC - { event, startA, "B", "C" }; - - { - auto const& Cs = AtoC.assPtrs(makeAptr(0)); - static_assert - (std::is_same_v> const&>); - - BOOST_TEST(Cs.empty()); - } - - { - auto const& Cs = AtoC.assPtrs(makeAptr(1)); - - BOOST_TEST(Cs.empty()); - } - - { - auto const& Cs = AtoC.assPtrs(makeAptr(2)); - - BOOST_TEST(Cs.size() == 1); - if (Cs.size() > 0) BOOST_TEST(Cs[0] == makeCptr(4)); - } - - { - auto const& Cs = AtoC.assPtrs(makeAptr(3)); - - BOOST_TEST(Cs.empty()); - } - - { - auto const& Cs = AtoC.assPtrs(makeAptr(4)); - - BOOST_TEST(Cs.empty()); - } - - { - auto const& Cs = AtoC.assPtrs(makeAptr(5)); - static_assert - (std::is_same_v> const&>); - BOOST_TEST(Cs.empty()); - } - -} // AssnsCrosserStartList4_test() - - -//------------------------------------------------------------------------------ -void AssnsCrosserClassDocumentation_test() { - - /* - * The promise: - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} - * icarus::ns::util::AssnsCrosser const AtoC - * { event, art::InputTag{ "B" }, art::InputTag{ "C" } }; - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * or - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} - * using icarus::ns::util::hopTo; - * auto const AtoC = icarus::ns::util::makeAssnsCrosser - * (event, hopTo{ "B" }, hopTo{ "C" }); - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * or - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} - * using icarus::ns::util::startFrom, icarus::ns::util::hopTo; - * icarus::ns::util::AssnsCrosser const AtoC{ event - * , startFrom{} - * , hopTo{ "B" } - * , hopTo{ "C" } - * }; - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * or - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} - * using icarus::ns::util::startFrom, icarus::ns::util::hopTo; - * auto const AtoC = makeAssnsCrosser(event - * , startFrom{} - * , hopTo{ "B" } - * , hopTo{ "C" } - * ); - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * The latter describe more clearly the relation between the data types and - * their input tags. - * - * If there are two sets of associations between `DataTypeA` and `DataTypeB`, - * `"B:1"` and `"B:2"`, the following initializations will work: - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} - * icarus::ns::util::AssnsCrosser const AtoC - * { event, { "B:1", "B:2" }, { "C" } }; - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * or - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} - * using icarus::ns::util::hopTo; - * auto const AtoC = icarus::ns::util::makeAssnsCrosser( - * event, - * hopTo{ "B:1", "B:2" }, hopTo{ "C" } - * ); - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - */ - - using ExpectedAtoC_t - = icarus::ns::util::AssnsCrosser const; - - testing::mockup::Event const event = makeTestEvent1(); - - { - icarus::ns::util::AssnsCrosser const AtoC - [[maybe_unused]] - { event, art::InputTag{ "B" }, art::InputTag{ "C" } }; - static_assert(std::is_same_v); - } - - { - using icarus::ns::util::hopTo; - auto const AtoC = icarus::ns::util::makeAssnsCrosser - (event, hopTo{ "B" }, hopTo{ "C" }); - static_assert(std::is_same_v); - } - - { - using icarus::ns::util::startFrom, icarus::ns::util::hopTo; - icarus::ns::util::AssnsCrosser const AtoC{ event - , startFrom{} - , hopTo{ "B" } - , hopTo{ "C" } - }; - static_assert(std::is_same_v); - } - - { - using icarus::ns::util::startFrom, icarus::ns::util::hopTo; - auto const AtoC = makeAssnsCrosser(event - , startFrom{} - , hopTo{ "B" } - , hopTo{ "C" } - ); - static_assert(std::is_same_v); - } - - { - icarus::ns::util::AssnsCrosser const AtoC - { event, { "B:1", "B:2" }, { "C" } }; - static_assert(std::is_same_v); - } - - { - using icarus::ns::util::hopTo; - auto const AtoC = icarus::ns::util::makeAssnsCrosser( - event, - hopTo{ "B:1", "B:2" }, hopTo{ "C" } - ); - static_assert(std::is_same_v); - } - -} // AssnsCrosserClassDocumentation_test() - - -//------------------------------------------------------------------------------ -void InputSpecsClassDocumentation_test() { - - using icarus::ns::util::InputSpecs, icarus::ns::util::InputSpec; - - using AtoZ_t = icarus::ns::util::AssnsCrosser< - DataTypeA, DataTypeB, DataTypeC, DataTypeD, DataTypeE, DataTypeF - >; - - // the purpose is to confirm that this code compiles - using instantiated [[maybe_unused]] = decltype( - AtoZ_t{ std::declval() - - // implicit conversion to `art::InputTag`: - , InputSpecs{ "TagB" } - - // implicit conversion to `art::InputTag` then to `InputSpecs`: - , "TagC" - - // explicit vector of input tags (not recommended): - , InputSpecs{ std::vector{ "TagD1", "TagD2" } } - - // list of input tags, converted to `InputSpecs`: - , InputSpecs{ "TagE1", "TagE2" } - - // implicit list of input tags, converted to `InputSpecs`: - , { "TagF1", "TagF2" } - - } - ); - -} // InputSpecsClassDocumentation_test() - - -//------------------------------------------------------------------------------ -//--- The tests -//--- -BOOST_AUTO_TEST_CASE( AssnsCrosser1_testCase ) { - - AssnsCrosser1_test(); - -} // BOOST_AUTO_TEST_CASE( AssnsCrosser1_testCase ) - - -BOOST_AUTO_TEST_CASE( AssnsCrosser2_testCase ) { - - AssnsCrosser2_test(); - AssnsCrosserDiamond_test(); - -} // BOOST_AUTO_TEST_CASE( AssnsCrosser2_testCase ) - - -BOOST_AUTO_TEST_CASE( AssnsCrosser3_testCase ) { - - AssnsCrosser3_test(); - -} // BOOST_AUTO_TEST_CASE( AssnsCrosser3_testCase ) - - -BOOST_AUTO_TEST_CASE( AssnsCrosserInput_testCase ) { - - // tests with different input specification styles - AssnsCrosserInputList1_test(); - AssnsCrosser3withID_test(); - AssnsCrosser3withJump_test(); - AssnsCrosser3with2jumps_test(); - -} // BOOST_AUTO_TEST_CASE( AssnsCrosserInput_testCase ) - - -BOOST_AUTO_TEST_CASE( AssnsCrosserStart_testCase ) { - - // tests with different start specification styles - AssnsCrosserStartList1_test(); - AssnsCrosserStartList2_test(); - AssnsCrosserStartList3_test(); - AssnsCrosserStartList4_test(); - -} // BOOST_AUTO_TEST_CASE( AssnsCrosserStart_testCase ) - - -BOOST_AUTO_TEST_CASE( AssnsCrosserDocumentation_testCase ) { - - AssnsCrosserClassDocumentation_test(); - InputSpecsClassDocumentation_test(); - -} // BOOST_AUTO_TEST_CASE( AssnsCrosserDocumentation_testCase ) - - -//------------------------------------------------------------------------------ - diff --git a/test/Utilities/CMakeLists.txt b/test/Utilities/CMakeLists.txt index 8c6602d..87301e3 100644 --- a/test/Utilities/CMakeLists.txt +++ b/test/Utilities/CMakeLists.txt @@ -54,15 +54,6 @@ cet_test(TimeIntervalConfig_test USE_BOOST_UNIT fhiclcpp::fhiclcpp ) -cet_test(AssnsCrosser_test - LIBRARIES - icarusalg::Utilities - icarusalg::Test - canvas::canvas - cetlib::cetlib - USE_BOOST_UNIT - ) - cet_test(sortLike_test LIBRARIES icarusalg::Utilities