Skip to content

Commit

Permalink
Use std::vector instead of std::map for reading or writing an arb…
Browse files Browse the repository at this point in the history
…itrary number of collections. (#201)
  • Loading branch information
jmcarcell authored Jul 15, 2024
1 parent ad01f7c commit a3380b2
Show file tree
Hide file tree
Showing 11 changed files with 312 additions and 185 deletions.
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ endif()

# Set up C++ Standard
# ``-DCMAKE_CXX_STANDARD=<standard>`` when invoking CMake
set(CMAKE_CXX_STANDARD 17 CACHE STRING "")
set(CMAKE_CXX_STANDARD 20 CACHE STRING "")

if(NOT CMAKE_CXX_STANDARD MATCHES "17|20")
if(NOT CMAKE_CXX_STANDARD MATCHES "20")
message(FATAL_ERROR "Unsupported C++ standard: ${CMAKE_CXX_STANDARD}")
endif()

Expand Down
32 changes: 30 additions & 2 deletions k4FWCore/include/k4FWCore/Consumer.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@

#include "k4FWCore/FunctionalUtils.h"

#include <ranges>
#include <stdexcept>
#include <type_traits>
#include <utility>

Expand All @@ -41,8 +43,8 @@ namespace k4FWCore {
: Gaudi::Functional::details::DataHandleMixin<std::tuple<>, std::tuple<>, Traits_> {
using Gaudi::Functional::details::DataHandleMixin<std::tuple<>, std::tuple<>, Traits_>::DataHandleMixin;

static_assert(((std::is_base_of_v<podio::CollectionBase, In> || isMapToCollLike<In>::value) && ...),
"Consumer input types must be EDM4hep collections or maps to collections");
static_assert(((std::is_base_of_v<podio::CollectionBase, In> || isVectorLike_v<In>)&&...),
"Consumer input types must be EDM4hep collections or vectors of collection pointers");

template <typename T>
using InputHandle_t = Gaudi::Functional::details::InputHandle_t<Traits_, std::remove_pointer_t<T>>;
Expand Down Expand Up @@ -90,6 +92,32 @@ namespace k4FWCore {

// ... instead, they must implement the following operator
virtual void operator()(const In&...) const = 0;

/**
* @brief Get the input locations for a given input index
* @param i The index of the input
* @return A range of the input locations
*/
const auto inputLocations(int i) const {
if (i >= sizeof...(In)) {
throw std::out_of_range("Called inputLocations with an index out of range, index: " + std::to_string(i) +
", number of inputs: " + std::to_string(sizeof...(In)));
}
return m_inputLocations[i] | std::views::transform([](const DataObjID& id) -> const auto& { return id.key(); });
}
/**
* @brief Get the input locations for a given input name
* @param name The name of the input
* @return A range of the input locations
*/
const auto inputLocations(std::string_view name) const {
auto it = std::ranges::find_if(m_inputLocations, [&name](const auto& prop) { return prop.name() == name; });
if (it == m_inputLocations.end()) {
throw std::runtime_error("Called inputLocations with an unknown name");
}
return it->value() | std::views::transform([](const DataObjID& id) -> const auto& { return id.key(); });
}
static constexpr std::size_t inputLocationsSize() { return sizeof...(In); }
};

} // namespace details
Expand Down
64 changes: 27 additions & 37 deletions k4FWCore/include/k4FWCore/FunctionalUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@

// #include "GaudiKernel/CommonMessaging.h"

#include <map>
#include <memory>
#include <tuple>
#include <type_traits>
Expand Down Expand Up @@ -63,16 +62,21 @@ namespace k4FWCore {
return static_cast<const T>(*arg);
}

// Check if the type is a map like type, where map type is the special map
// Check if the type is a vector like type, where vector is the special
// type to have an arbitrary number of collections as input or output:
// std::map<std::string, Coll> where Coll is the collection type
template <typename T> struct isMapToCollLike : std::false_type {};
// std::vector<Coll> where Coll is the collection type for output
// and const std::vector<const Coll*>& for input
template <typename T> struct isVectorLike : std::false_type {};

template <typename Value>
requires std::is_base_of_v<podio::CollectionBase, std::remove_cvref_t<Value>>
struct isMapToCollLike<std::map<std::string, Value>> : std::true_type {};
struct isVectorLike<std::vector<Value*>> : std::true_type {};

template <class T> inline constexpr bool isMapToCollLike_v = isMapToCollLike<T>::value;
template <typename Value>
requires std::is_base_of_v<podio::CollectionBase, std::remove_cvref_t<Value>>
struct isVectorLike<std::vector<Value>> : std::true_type {};

template <class T> inline constexpr bool isVectorLike_v = isVectorLike<T>::value;

// transformType function to transform the types from the ones that the user wants
// like edm4hep::MCParticleCollection, to the ones that are actually stored in the
Expand All @@ -83,7 +87,7 @@ namespace k4FWCore {
};

template <typename T>
requires std::is_base_of_v<podio::CollectionBase, T> || isMapToCollLike_v<T>
requires std::is_base_of_v<podio::CollectionBase, T> || isVectorLike_v<T>
struct transformType<T> {
using type = std::shared_ptr<podio::CollectionBase>;
};
Expand All @@ -105,27 +109,26 @@ namespace k4FWCore {
static auto apply(const Algorithm& algo, const EventContext&, Handles& handles) {
auto inputTuple = std::tuple<addPtrIfColl<In>...>();

// Build the input tuple by picking up either std::map with an arbitrary
// Build the input tuple by picking up either std::vector with an arbitrary
// number of collections or single collections
readMapInputs<0, In...>(handles, &algo, inputTuple);
readVectorInputs<0, In...>(handles, &algo, inputTuple);

return std::apply(
[&](const auto&... input) { return algo(maybeTransformToEDM4hep<decltype(input)>(input)...); }, inputTuple);
}
};

template <size_t Index, typename... In, typename... Handles, typename InputTuple>
void readMapInputs(const std::tuple<Handles...>& handles, auto thisClass, InputTuple& inputTuple) {
void readVectorInputs(const std::tuple<Handles...>& handles, auto thisClass, InputTuple& inputTuple) {
if constexpr (Index < sizeof...(Handles)) {
if constexpr (isMapToCollLike_v<std::tuple_element_t<Index, std::tuple<In...>>>) {
// In case of map types like std::map<std::string, edm4hep::MCParticleCollection&>
// we have to remove the reference to get the actual type
if constexpr (isVectorLike_v<std::tuple_element_t<Index, std::tuple<In...>>>) {
// Bare EDM4hep type, without pointers
using EDM4hepType =
std::remove_reference_t<typename std::tuple_element_t<Index, std::tuple<In...>>::mapped_type>;
auto inputMap = std::map<std::string, const EDM4hepType&>();
std::remove_pointer_t<typename std::tuple_element_t<Index, std::tuple<In...>>::value_type>;
auto inputMap = std::vector<const EDM4hepType*>();
for (auto& handle : std::get<Index>(handles)) {
auto in = get(handle, thisClass, Gaudi::Hive::currentContext());
inputMap.emplace(handle.objKey(), *static_cast<EDM4hepType*>(in.get()));
inputMap.push_back(static_cast<EDM4hepType*>(in.get()));
}
std::get<Index>(inputTuple) = std::move(inputMap);

Expand Down Expand Up @@ -159,36 +162,23 @@ namespace k4FWCore {
}

// Recursive call for the next index
readMapInputs<Index + 1, In...>(handles, thisClass, inputTuple);
readVectorInputs<Index + 1, In...>(handles, thisClass, inputTuple);
}
}

template <size_t Index, typename... Out, typename... Handles>
void putMapOutputs(std::tuple<Handles...>&& handles, const auto& m_outputs, auto thisClass) {
void putVectorOutputs(std::tuple<Handles...>&& handles, const auto& m_outputs, auto thisClass) {
if constexpr (Index < sizeof...(Handles)) {
if constexpr (isMapToCollLike_v<std::tuple_element_t<Index, std::tuple<Out...>>>) {
if constexpr (isVectorLike_v<std::tuple_element_t<Index, std::tuple<Out...>>>) {
int i = 0;
if (std::get<Index>(handles).size() != std::get<Index>(m_outputs).size()) {
std::string msg = "Size of the output map " + std::to_string(std::get<Index>(handles).size()) +
std::string msg = "Size of the output vector " + std::to_string(std::get<Index>(handles).size()) +
" with type " + typeid(std::get<Index>(handles)).name() +
" does not match the expected size from the steering file " +
std::to_string(std::get<Index>(m_outputs).size()) + ". Expected the collections: ";
for (auto& out : std::get<Index>(m_outputs)) {
msg += out.objKey() + " ";
}
msg += " but got the collections: ";
for (auto& out : std::get<Index>(handles)) {
msg += out.first + " ";
}
std::to_string(std::get<Index>(m_outputs).size());
throw GaudiException(thisClass->name(), msg, StatusCode::FAILURE);
}
for (auto& [key, val] : std::get<Index>(handles)) {
if (key != std::get<Index>(m_outputs)[i].objKey()) {
throw GaudiException(thisClass->name(),
"Output key in the map \"" + key +
"\" does not match the expected key from the steering file \"" +
std::get<Index>(m_outputs)[i].objKey() + "\"",
StatusCode::FAILURE);
}
for (auto& val : std::get<Index>(handles)) {
Gaudi::Functional::details::put(std::get<Index>(m_outputs)[i], convertToSharedPtr(std::move(val)));
i++;
}
Expand All @@ -198,7 +188,7 @@ namespace k4FWCore {
}

// Recursive call for the next index
putMapOutputs<Index + 1, Out...>(std::move(handles), m_outputs, thisClass);
putVectorOutputs<Index + 1, Out...>(std::move(handles), m_outputs, thisClass);
}
}

Expand Down
127 changes: 113 additions & 14 deletions k4FWCore/include/k4FWCore/Transformer.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@

// #include "GaudiKernel/CommonMessaging.h"

#include <ranges>
#include <stdexcept>
#include <type_traits>
#include <utility>

Expand All @@ -41,10 +43,11 @@ namespace k4FWCore {
: Gaudi::Functional::details::DataHandleMixin<std::tuple<>, std::tuple<>, Traits_> {
using Gaudi::Functional::details::DataHandleMixin<std::tuple<>, std::tuple<>, Traits_>::DataHandleMixin;

static_assert(((std::is_base_of_v<podio::CollectionBase, In> || isMapToCollLike<In>::value) && ...),
"Transformer and Producer input types must be EDM4hep collections or maps to collections");
static_assert((std::is_base_of_v<podio::CollectionBase, Out> || isMapToCollLike<Out>::value),
"Transformer and Producer output types must be EDM4hep collections or maps to collections");
static_assert(
((std::is_base_of_v<podio::CollectionBase, In> || isVectorLike_v<In>)&&...),
"Transformer and Producer input types must be EDM4hep collections or vectors of collection pointers");
static_assert((std::is_base_of_v<podio::CollectionBase, Out> || isVectorLike_v<Out>),
"Transformer and Producer output types must be EDM4hep collections or vectors of collections");

template <typename T>
using InputHandle_t = Gaudi::Functional::details::InputHandle_t<Traits_, std::remove_pointer_t<T>>;
Expand All @@ -54,7 +57,7 @@ namespace k4FWCore {
std::tuple<std::vector<InputHandle_t<typename transformType<In>::type>>...> m_inputs;
std::tuple<std::vector<OutputHandle_t<typename transformType<Out>::type>>> m_outputs;
std::array<Gaudi::Property<std::vector<DataObjID>>, sizeof...(In)> m_inputLocations{};
std::array<Gaudi::Property<std::vector<DataObjID>>, 1> m_outputLocations{};
Gaudi::Property<std::vector<DataObjID>> m_outputLocations{};

using base_class = Gaudi::Functional::details::DataHandleMixin<std::tuple<>, std::tuple<>, Traits_>;

Expand Down Expand Up @@ -85,10 +88,7 @@ namespace k4FWCore {
this, std::get<J>(outputs).first, to_DataObjID(std::get<J>(outputs).second),
[this](Gaudi::Details::PropertyBase&) {
std::vector<OutputHandle_t<typename transformType<Out>::type>> h;
// Is this needed?
// std::sort(this->m_outputLocations[J].value().begin(), this->m_outputLocations[J].value().end(),
// [](const DataObjID& a, const DataObjID& b) { return a.key() < b.key(); });
for (auto& inpID : this->m_outputLocations[J].value()) {
for (auto& inpID : this->m_outputLocations.value()) {
if (inpID.key().empty()) {
continue;
}
Expand All @@ -111,9 +111,9 @@ namespace k4FWCore {
// derived classes are NOT allowed to implement execute ...
StatusCode execute(const EventContext& ctx) const override final {
try {
if constexpr (isMapToCollLike<Out>::value) {
if constexpr (isVectorLike<Out>::value) {
std::tuple<Out> tmp = filter_evtcontext_tt<In...>::apply(*this, ctx, this->m_inputs);
putMapOutputs<0, Out>(std::move(tmp), m_outputs, this);
putVectorOutputs<0, Out>(std::move(tmp), m_outputs, this);
} else {
Gaudi::Functional::details::put(
std::get<0>(this->m_outputs)[0],
Expand All @@ -126,6 +126,52 @@ namespace k4FWCore {
}
}

/**
* @brief Get the input locations for a given input index
* @param i The index of the input
* @return A range of the input locations
*/
auto inputLocations(int i) const {
if (i >= sizeof...(In)) {
throw std::out_of_range("Called inputLocations with an index out of range, index: " + std::to_string(i) +
", number of inputs: " + std::to_string(sizeof...(In)));
}
return m_inputLocations[i] | std::views::transform([](const DataObjID& id) -> const auto& { return id.key(); });
}
/**
* @brief Get the input locations for a given input name
* @param name The name of the input
* @return A range of the input locations
*/
const auto inputLocations(std::string_view name) const {
auto it = std::ranges::find_if(m_inputLocations, [&name](const auto& prop) { return prop.name() == name; });
if (it == m_inputLocations.end()) {
throw std::runtime_error("Called inputLocations with an unknown name");
}
return it->value() | std::views::transform([](const DataObjID& id) -> const auto& { return id.key(); });
}

/**
* @brief Get the output locations for a given output index
* @param i The index of the output
* @return A range of the output locations
*/
auto outputLocations() const {
return m_outputLocations | std::views::transform([](const DataObjID& id) -> const auto& { return id.key(); });
}
/**
* @brief Get the output locations for a given output name
* @param name The name of the output
* @return A range of the output locations
*/
const auto outputLocations(std::string_view name) const {
if (name != m_outputLocations.name()) {
throw std::runtime_error("Called outputLocations with an unknown name");
}
return m_outputLocations | std::views::transform([](const DataObjID& id) -> const auto& { return id.key(); });
}
static constexpr std::size_t inputLocationsSize() { return sizeof...(In); }

// ... instead, they must implement the following operator
virtual Out operator()(const In&...) const = 0;
};
Expand All @@ -137,9 +183,9 @@ namespace k4FWCore {
: Gaudi::Functional::details::DataHandleMixin<std::tuple<>, std::tuple<>, Traits_> {
using Gaudi::Functional::details::DataHandleMixin<std::tuple<>, std::tuple<>, Traits_>::DataHandleMixin;

static_assert(((std::is_base_of_v<podio::CollectionBase, In> || isMapToCollLike<In>::value) && ...),
static_assert(((std::is_base_of_v<podio::CollectionBase, In> || isVectorLike<In>::value) && ...),
"Transformer and Producer input types must be EDM4hep collections or maps to collections");
static_assert(((std::is_base_of_v<podio::CollectionBase, Out> || isMapToCollLike<Out>::value) && ...),
static_assert(((std::is_base_of_v<podio::CollectionBase, Out> || isVectorLike<Out>::value) && ...),
"Transformer and Producer output types must be EDM4hep collections or maps to collections");

template <typename T>
Expand Down Expand Up @@ -202,14 +248,67 @@ namespace k4FWCore {
StatusCode execute(const EventContext& ctx) const override final {
try {
auto tmp = filter_evtcontext_tt<In...>::apply(*this, ctx, this->m_inputs);
putMapOutputs<0, Out...>(std::move(tmp), m_outputs, this);
putVectorOutputs<0, Out...>(std::move(tmp), m_outputs, this);
return Gaudi::Functional::FilterDecision::PASSED;
} catch (GaudiException& e) {
(e.code() ? this->warning() : this->error()) << e.tag() << " : " << e.message() << endmsg;
return e.code();
}
}

/**
* @brief Get the input locations for a given input index
* @param i The index of the input
* @return A range of the input locations
*/
const auto inputLocations(int i) const {
if (i >= sizeof...(In)) {
throw std::out_of_range("Called inputLocations with an index out of range, index: " + std::to_string(i) +
", number of inputs: " + std::to_string(sizeof...(In)));
}
return m_inputLocations[i] | std::views::transform([](const DataObjID& id) -> const auto& { return id.key(); });
}
/**
* @brief Get the input locations for a given input name
* @param name The name of the input
* @return A range of the input locations
*/
const auto inputLocations(std::string_view name) const {
auto it = std::ranges::find_if(m_inputLocations, [&name](const auto& prop) { return prop.name() == name; });
if (it == m_inputLocations.end()) {
throw std::runtime_error("Called inputLocations with an unknown name");
}
return it->value() | std::views::transform([](const DataObjID& id) -> const auto& { return id.key(); });
}

/**
* @brief Get the output locations for a given output index
* @param i The index of the output
* @return A range of the output locations
*/
auto outputLocations(int i) const {
if (i >= sizeof...(Out)) {
throw std::out_of_range("Called outputLocations with an index out of range");
}
return m_outputLocations[i] |
std::views::transform([](const DataObjID& id) -> const auto& { return id.key(); });
}
/**
* @brief Get the output locations for a given output name
* @param name The name of the output
* @return A range of the output locations
*/
const auto outputLocations(std::string_view name) const {
auto it = std::ranges::find_if(m_outputLocations.begin(), m_outputLocations.end(),
[&name](const auto& prop) { return prop.name() == name; });
if (it == m_outputLocations.end()) {
throw std::runtime_error("Called outputLocations with an unknown name");
}
return it->value() | std::views::transform([](const DataObjID& id) -> const auto& { return id.key(); });
}
static constexpr std::size_t inputLocationsSize() { return sizeof...(In); }
static constexpr std::size_t outputLocationsSize() { return sizeof...(Out); }

// ... instead, they must implement the following operator
virtual std::tuple<Out...> operator()(const In&...) const = 0;
};
Expand Down
Loading

0 comments on commit a3380b2

Please sign in to comment.