algorithms  2.2.0
Framework-independent algorithms for the EIC
Loading...
Searching...
No Matches
algorithm.h
Go to the documentation of this file.
1// SPDX-License-Identifier: LGPL-3.0-or-later
2// Copyright (C) 2022 Wouter Deconinck, Sylvester Joosten
3//
4// Algorithm base class, defined as a template with a tuple of Input<> and Output<>
5// parameters.
6//
7// Known types are:
8// - Normal data type T
9// - Optional data type std::optional<T>
10// - Vector of normal data std::vector<T>
11// - Vector of optional data std::vector<std::optional<T>>
12//
13// For input data, this then selects:
14// - T --> gsl::not_null<const T*> (NOT allowed to be null)
15// - optional<T> --> const T* (allowed to be null)
16// - vector<T> --> std::vector<gsl::not_null<const T*>> (N arguments, NOT allowed
17// to be null, but can be
18// zero length)
19// - vector<optional<T>> --> std::vector<const T*> (N arguments, each allowed to be null,
20// and can be zero length)
21//
22// Same for output data, but replace `const T*` with `T*` (mutable) everywhere.
23//
24// The ::process() algorithm is then provided with a tuple of both the input and the
25// output pointers according to this scheme.
26//
27// Finally, provides provides utility traits to determine if a type Input<T...> or Output<T...> are
28// an Input or Output Type (is_input_v<U> and is_output_v<U>)
29//
30#pragma once
31
32#include <array>
33#include <functional>
34#include <map>
35#include <memory>
36#include <mutex>
37#include <optional>
38#include <string>
39#include <string_view>
40#include <tuple>
41#include <typeindex>
42#include <typeinfo>
43#include <vector>
44
46#include <algorithms/logger.h>
47#include <algorithms/name.h>
48#include <algorithms/property.h>
49#include <algorithms/service.h>
51
52namespace algorithms {
53
54// T should either be the desired input type, a std::vector<> of the desired input type,
55// a std::optional<> of the desired input type, or a std::vector<std::optional<>> of the
56// desired input type
57template <class... T> struct Input : std::tuple<input_type_t<T>...> {
58 constexpr static const size_t kSize = sizeof...(T);
59 using value_type = std::tuple<input_type_t<T>...>;
60 using data_type = std::tuple<T...>;
61 using key_type = std::array<const std::string, kSize>;
62};
63template <class... T> struct Output : std::tuple<output_type_t<T>...> {
64 constexpr static const size_t kSize = sizeof...(T);
65 using value_type = std::tuple<output_type_t<T>...>;
66 using data_type = std::tuple<T...>;
67 using key_type = std::array<const std::string, kSize>;
68};
69
70class AlgorithmBase : public PropertyMixin, public LoggerMixin, public NameMixin {
71public:
72 AlgorithmBase(std::string_view name, std::string_view description)
74};
75
76// TODO: C++20 Concepts version for better error handling
77template <class InputType, class OutputType> class Algorithm : public AlgorithmBase {
78public:
79 using input_type = InputType;
80 using output_type = OutputType;
82 using Input = typename input_type::value_type;
83 using Output = typename output_type::value_type;
84 using InputNames = typename input_type::key_type;
85 using OutputNames = typename output_type::key_type;
86
87 Algorithm(std::string_view name, const InputNames& input_names, const OutputNames& output_names,
88 std::string_view description)
90 , m_input_names{input_names}
91 , m_output_names{output_names} {}
92
93 virtual ~Algorithm() {}
94 virtual void init() {}
95 virtual void process(const Input&, const Output&) const {}
96
97 const InputNames& inputNames() const { return m_input_names; }
98 const OutputNames& outputNames() const { return m_output_names; }
99
100private:
101 const InputNames m_input_names;
102 const OutputNames m_output_names;
103};
104
105// Algorithm service that stores factories for different algorithms indexed by their
106// underlying algorithm_type. This allows a framework to only need to know about every
107// algorithm_type (algorithm signature) once, which is sufficient to constrain all
108// compile-time code needed.
109class AlgorithmSvc : public LoggedService<AlgorithmSvc> {
110public:
111 void init() {}
112
113 // Add a new factory for an algorithm with a specified type. The full demangled type name
114 // will be used as identifier for the factory
115 template <class Algo> void add() {
116 using AlgoType = typename Algo::algorithm_type;
117 std::lock_guard<std::mutex> lock{m_mutex};
118 auto& factories = m_factories[typeid(AlgoType)];
119 // do nothing if already there
120 factories.try_emplace(detail::demangledName<Algo>(),
121 []() -> std::unique_ptr<AlgorithmBase> { return std::make_unique<Algo>(); });
122 }
123
124 // Get a new owning pointer to an instance of an algorithm.
125 // Throws if the resource isn't available, or if we aren't fully ready yet.
126 template <class AlgoType> std::unique_ptr<AlgoType> get(std::string_view name) const {
127 ensureReady();
128 std::lock_guard<std::mutex> lock{m_mutex};
129 const auto* factory = find<AlgoType>(name);
130 if (factory == nullptr) {
131 raise(fmt::format("No factory with name {} and type {} registered with the AlgorithmSvc",
133 }
134 // This creates an object and gives ownership to the unique_ptr
135 return std::unique_ptr<AlgoType>(static_cast<AlgoType*>((*factory)().release()));
136 }
137 // Return a vector of the names of all available algorithms of a certain type
138 // Just return an empty vector if no names are present (don't throw any exceptions
139 // for missing entries here).
140 // Throws if we aren't fully ready yet.
141 template <class AlgoType> std::vector<std::string> ls() const {
142 ensureReady();
143 std::lock_guard<std::mutex> lock{m_mutex};
144 std::vector<std::string> ret;
145 if (const auto it = m_factories.find(typeid(AlgoType)); it != m_factories.end()) {
146 for (const auto& [name, factory] : it->second) {
147 ret.push_back(name);
148 }
149 }
150 return ret;
151 }
152
153private:
154 using factory_type = std::function<std::unique_ptr<AlgorithmBase>()>;
155
156 // Find the factory of type AlgoType with a specific name, or nullptr if there is none.
157 // Throws if the name is invalid
158 template <class AlgoType> const factory_type* find(std::string_view name) const {
159 if (name.empty()) {
160 raise(fmt::format("Invalid name provided: '{}'", name));
161 }
162 const auto it = m_factories.find(typeid(AlgoType));
163 if (it == m_factories.end()) {
164 return nullptr;
165 }
166 const auto factory = it->second.find(name);
167 return (factory != it->second.end()) ? &factory->second : nullptr;
168 }
169 // Throw an exception if we are not ready as factory to avoid giving incomplete factory
170 // information to the caller
171 void ensureReady() const {
172 if (!ready()) {
173 raise("Attempt to use AlgorithmSvc, but service not yet marked as ready");
174 }
175 }
176
177 mutable std::mutex m_mutex;
178 std::map<std::type_index, std::map<std::string, factory_type, std::less<>>> m_factories;
179
181};
182
183namespace detail {
184 template <class T> struct is_input : std::false_type {};
185 template <class... T> struct is_input<Input<T...>> : std::true_type {};
186 template <class T> struct is_output : std::false_type {};
187 template <class... T> struct is_output<Output<T...>> : std::true_type {};
188} // namespace detail
189template <class T> constexpr bool is_input_v = detail::is_input<T>::value;
190template <class T> constexpr bool is_output_v = detail::is_output<T>::value;
191
192} // namespace algorithms
193
AlgorithmBase(std::string_view name, std::string_view description)
Definition algorithm.h:72
std::vector< std::string > ls() const
Definition algorithm.h:141
std::unique_ptr< AlgoType > get(std::string_view name) const
Definition algorithm.h:126
typename input_type::key_type InputNames
Definition algorithm.h:84
virtual void init()
Definition algorithm.h:94
Algorithm algorithm_type
Definition algorithm.h:81
typename input_type::value_type Input
Definition algorithm.h:82
OutputType output_type
Definition algorithm.h:80
const OutputNames & outputNames() const
Definition algorithm.h:98
typename output_type::key_type OutputNames
Definition algorithm.h:85
const InputNames & inputNames() const
Definition algorithm.h:97
typename output_type::value_type Output
Definition algorithm.h:83
Algorithm(std::string_view name, const InputNames &input_names, const OutputNames &output_names, std::string_view description)
Definition algorithm.h:87
virtual void process(const Input &, const Output &) const
Definition algorithm.h:95
LoggedService(std::string_view name)
Definition logger.h:273
LoggerMixin(std::string_view caller, const LogLevel threshold=LogSvc::instance().defaultLevel())
Definition logger.h:180
void raise(std::string_view msg) const
Definition logger.h:238
std::string_view description() const
Definition name.h:18
NameMixin(std::string_view name, std::string_view description)
Definition name.h:15
std::string_view name() const
Definition name.h:17
bool ready() const
Definition service.h:46
#define ALGORITHMS_DEFINE_LOGGED_SERVICE(className)
Definition logger.h:278
std::string demangledName()
Definition demangle.h:17
Configurable PropertyMixin
Definition property.h:206
constexpr bool is_input_v
Definition algorithm.h:189
constexpr bool is_output_v
Definition algorithm.h:190
std::tuple< input_type_t< T >... > value_type
Definition algorithm.h:59
static constexpr const size_t kSize
Definition algorithm.h:58
std::array< const std::string, kSize > key_type
Definition algorithm.h:61
std::tuple< T... > data_type
Definition algorithm.h:60
static constexpr const size_t kSize
Definition algorithm.h:64
std::tuple< T... > data_type
Definition algorithm.h:66
std::tuple< output_type_t< T >... > value_type
Definition algorithm.h:65
std::array< const std::string, kSize > key_type
Definition algorithm.h:67