Line | Branch | Exec | Source |
---|---|---|---|
1 | #pragma once | ||
2 | |||
3 | #include <mutex> | ||
4 | #include <optional> | ||
5 | #include <set> | ||
6 | |||
7 | #include <hipo4/bank.h> | ||
8 | |||
9 | |||
10 | #include "iguana/algorithms/AlgorithmBoilerplate.h" | ||
11 | #include "iguana/services/YAMLReader.h" | ||
12 | #include <iguana/services/GlobalParam.h> | ||
13 | |||
14 | namespace iguana { | ||
15 | |||
16 | /// Option value variant type | ||
17 | /* NOTE: if you modify this, you also must modify: | ||
18 | * - [ ] `PrintOptionValue` | ||
19 | * - [ ] Template specializations in this class | ||
20 | * - [ ] Template specializations in `YAMLReader` or `ConfigFileReader`, and `ConcurrentParam` | ||
21 | * - [ ] Add new tests, if you added new types | ||
22 | */ | ||
23 | using option_t = std::variant< | ||
24 | int, | ||
25 | double, | ||
26 | std::string, | ||
27 | std::vector<int>, | ||
28 | std::vector<double>, | ||
29 | std::vector<std::string>>; | ||
30 | |||
31 | /// @brief Base class for all algorithms to inherit from | ||
32 | /// | ||
33 | /// This is the base class for all algorithms. It provides common members, such as | ||
34 | /// a logger instance and options data structure. Algorithm implementations must: | ||
35 | /// - inherit from this base class | ||
36 | /// - override the methods `Algorithm::Start`, `Algorithm::Run` and `Algorithm::Stop` | ||
37 | /// | ||
38 | /// See existing algorithms for examples. | ||
39 | class Algorithm : public Object | ||
40 | { | ||
41 | |||
42 | public: | ||
43 | |||
44 | /// @param name the unique name for a derived class instance | ||
45 | 72 | Algorithm(std::string_view name) | |
46 | 72 | : Object(name) | |
47 | 72 | , m_rows_only(false) | |
48 |
1/2✓ Branch 0 taken 72 times.
✗ Branch 1 not taken.
|
72 | , m_default_config_file("") |
49 |
1/2✓ Branch 0 taken 72 times.
✗ Branch 1 not taken.
|
72 | , o_user_config_file("") |
50 |
2/4✓ Branch 0 taken 72 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 72 times.
✗ Branch 3 not taken.
|
144 | , o_user_config_dir("") |
51 | 72 | {} | |
52 | 144 | virtual ~Algorithm() {} | |
53 | |||
54 | /// @brief Initialize this algorithm before any events are processed, with the intent to process _banks_ | ||
55 | /// | ||
56 | /// use this method if you intend to use `Algorithm::Run`. | ||
57 | /// @param banks the list of banks this algorithm will use, so that `Algorithm::Run` can cache the indices | ||
58 | /// of the banks that it needs | ||
59 | virtual void Start(hipo::banklist& banks) = 0; | ||
60 | |||
61 | /// @brief Initialize this algorithm before any events are processed, with the intent to process _bank rows_ rather than full banks; | ||
62 | /// | ||
63 | /// use this method if you intend to use "action functions" instead of `Algorithm::Run`. | ||
64 | void Start(); | ||
65 | |||
66 | /// @brief Run this algorithm for an event. | ||
67 | /// @param banks the list of banks to process | ||
68 | virtual void Run(hipo::banklist& banks) const = 0; | ||
69 | |||
70 | /// @brief Finalize this algorithm after all events are processed. | ||
71 | virtual void Stop() = 0; | ||
72 | |||
73 | /// Set an option specified by the user. If the option name is `"log"`, the log level of the `Logger` | ||
74 | /// owned by this algorithm will be changed to the specified value. | ||
75 | /// @param key the name of the option | ||
76 | /// @param val the value to set | ||
77 | /// @returns the value that has been set (if needed, _e.g._, when `val` is an rvalue) | ||
78 | template <typename OPTION_TYPE> | ||
79 | 55 | OPTION_TYPE SetOption(std::string const& key, const OPTION_TYPE val) | |
80 | { | ||
81 | // FIXME: this template is not specialized, to be friendlier to python `cppyy` bindings | ||
82 |
2/2✓ Branch 0 taken 29 times.
✓ Branch 1 taken 26 times.
|
55 | if(key == "log") { |
83 | if constexpr(std::disjunction< | ||
84 | std::is_same<OPTION_TYPE, std::string>, | ||
85 | std::is_same<OPTION_TYPE, char const*>, | ||
86 | std::is_same<OPTION_TYPE, Logger::Level>>::value) | ||
87 | 29 | m_log->SetLevel(val); | |
88 | else | ||
89 | ✗ | m_log->Error("Option '{}' must be a string or a Logger::Level", key); | |
90 | } | ||
91 | 52 | m_option_cache[key] = val; | |
92 | 55 | return val; | |
93 | } | ||
94 | |||
95 | /// Get the value of a scalar option | ||
96 | /// @param key the unique key name of this option, for caching; if empty, the option will not be cached | ||
97 | /// @param node_path the `YAML::Node` identifier path to search for this option in the config files; if empty, it will just use `key` | ||
98 | /// @returns the scalar option | ||
99 | template <typename OPTION_TYPE> | ||
100 | OPTION_TYPE GetOptionScalar(std::string const& key, YAMLReader::node_path_t node_path = {}) const; | ||
101 | |||
102 | /// Get the value of a vector option | ||
103 | /// @param key the unique key name of this option, for caching; if empty, the option will not be cached | ||
104 | /// @param node_path the `YAML::Node` identifier path to search for this option in the config files; if empty, it will just use `key` | ||
105 | /// @returns the vector option | ||
106 | template <typename OPTION_TYPE> | ||
107 | std::vector<OPTION_TYPE> GetOptionVector(std::string const& key, YAMLReader::node_path_t node_path = {}) const; | ||
108 | |||
109 | /// Get the value of a vector option, and convert it to `std::set` | ||
110 | /// @param key the unique key name of this option | ||
111 | /// @param node_path the `YAML::Node` identifier path to search for this option in the config files; if empty, it will just use `key` | ||
112 | /// @returns the vector option converted to `std::set` | ||
113 | template <typename OPTION_TYPE> | ||
114 | std::set<OPTION_TYPE> GetOptionSet(std::string const& key, YAMLReader::node_path_t node_path = {}) const; | ||
115 | |||
116 | /// Set the name of this algorithm | ||
117 | /// @param name the new name | ||
118 | void SetName(std::string_view name); | ||
119 | |||
120 | /// Get a reference to this algorithm's configuration (`YAMLReader`) | ||
121 | /// @returns the configuration | ||
122 | std::unique_ptr<YAMLReader> const& GetConfig() const; | ||
123 | |||
124 | /// Set a custom `YAMLReader` to use for this algorithm | ||
125 | /// @param yaml_config the custom `YAMLReader` instance | ||
126 | void SetConfig(std::unique_ptr<YAMLReader>&& yaml_config); | ||
127 | |||
128 | /// Set a custom configuration file for this algorithm | ||
129 | /// @see `Algorithm::SetConfigDirectory` | ||
130 | /// @param name the configuration file name | ||
131 | void SetConfigFile(std::string const& name); | ||
132 | |||
133 | /// Set a custom configuration file directory for this algorithm | ||
134 | /// @see `Algorithm::SetConfigFile` | ||
135 | /// @param name the directory name | ||
136 | void SetConfigDirectory(std::string const& name); | ||
137 | |||
138 | protected: // methods | ||
139 | |||
140 | /// Parse YAML configuration files. Sets `m_yaml_config`. | ||
141 | void ParseYAMLConfig(); | ||
142 | |||
143 | /// Get the reference to a bank from a `hipo::banklist`; optionally checks if the bank name matches the expectation | ||
144 | /// @param banks the `hipo::banklist` from which to get the specified bank | ||
145 | /// @param idx the index of `banks` of the specified bank | ||
146 | /// @param expected_bank_name if specified, checks that the specified bank has this name | ||
147 | /// @return a reference to the bank | ||
148 | hipo::bank& GetBank(hipo::banklist& banks, hipo::banklist::size_type const idx, std::string const& expected_bank_name = "") const noexcept(false); | ||
149 | |||
150 | /// Get the index of a bank in a `hipo::banklist`; throws an exception if the bank is not found | ||
151 | /// @param banks the list of banks this algorithm will use | ||
152 | /// @param bank_name the name of the bank | ||
153 | /// returns the `hipo::banklist` index of the bank | ||
154 | hipo::banklist::size_type GetBankIndex(hipo::banklist& banks, std::string const& bank_name) const noexcept(false); | ||
155 | |||
156 | /// Create a new bank and push it to the bank list | ||
157 | /// @param [out] banks the `hipo::banklist` onto which the new bank will be pushed | ||
158 | /// @param [out] bank_idx will be set to the `hipo::banklist` index of the new bank | ||
159 | /// @param [in] bank_name the new bank name | ||
160 | /// @param [in] schema_def a list of variables for the schema | ||
161 | /// @param [in] group_id the group ID for the schema | ||
162 | /// @param [in] item_id the item ID for the schema | ||
163 | /// @returns the bank's schema | ||
164 | hipo::schema CreateBank( | ||
165 | hipo::banklist& banks, | ||
166 | hipo::banklist::size_type& bank_idx, | ||
167 | std::string const& bank_name, | ||
168 | std::vector<std::string> schema_def, | ||
169 | int group_id, // FIXME: generalize group_id and item_id setting | ||
170 | int item_id) const noexcept(false); | ||
171 | |||
172 | /// Dump all banks in a `hipo::banklist` | ||
173 | /// @param banks the banks to show | ||
174 | /// @param message if specified, print a header message | ||
175 | /// @param level the log level | ||
176 | void ShowBanks(hipo::banklist& banks, std::string_view message = "", Logger::Level const level = Logger::trace) const; | ||
177 | |||
178 | /// Dump a single bank | ||
179 | /// @param bank the bank to show | ||
180 | /// @param message if specified, print a header message | ||
181 | /// @param level the log level | ||
182 | void ShowBank(hipo::bank& bank, std::string_view message = "", Logger::Level const level = Logger::trace) const; | ||
183 | |||
184 | /// Get an option from the option cache | ||
185 | /// @param key the key name associated with this option | ||
186 | /// @returns the option value, if found (using `std::optional`) | ||
187 | template <typename OPTION_TYPE> | ||
188 | std::optional<OPTION_TYPE> GetCachedOption(std::string const& key) const; | ||
189 | |||
190 | private: // methods | ||
191 | |||
192 | /// Prepend `node_path` with the full algorithm name. If `node_path` is empty, set it to `{key}`. | ||
193 | /// @param key the key name for this option | ||
194 | /// @param node_path the `YAMLReader::node_path_t` to prepend | ||
195 | void CompleteOptionNodePath(std::string const& key, YAMLReader::node_path_t& node_path) const; | ||
196 | |||
197 | // PrintOptionValue: overloaded for different value types | ||
198 | void PrintOptionValue(std::string const& key, int const& val, Logger::Level const level = Logger::debug, std::string_view prefix = "OPTION") const; | ||
199 | void PrintOptionValue(std::string const& key, double const& val, Logger::Level const level = Logger::debug, std::string_view prefix = "OPTION") const; | ||
200 | void PrintOptionValue(std::string const& key, std::string const& val, Logger::Level const level = Logger::debug, std::string_view prefix = "OPTION") const; | ||
201 | void PrintOptionValue(std::string const& key, std::vector<int> const& val, Logger::Level const level = Logger::debug, std::string_view prefix = "OPTION") const; | ||
202 | void PrintOptionValue(std::string const& key, std::vector<double> const& val, Logger::Level const level = Logger::debug, std::string_view prefix = "OPTION") const; | ||
203 | void PrintOptionValue(std::string const& key, std::vector<std::string> const& val, Logger::Level const level = Logger::debug, std::string_view prefix = "OPTION") const; | ||
204 | |||
205 | protected: // members | ||
206 | |||
207 | /// Class name of this algorithm | ||
208 | std::string m_class_name; | ||
209 | |||
210 | /// If true, algorithm can only operate on bank _rows_; `Algorithm::GetBank`, and therefore `Algorithm::Run`, cannot be called | ||
211 | bool m_rows_only; | ||
212 | |||
213 | /// Default configuration file name | ||
214 | std::string m_default_config_file; | ||
215 | |||
216 | /// User's configuration file name, which may override the default configuration file, `m_default_config_file`. | ||
217 | /// Set it with `Algorithm::SetConfigFile` | ||
218 | std::string o_user_config_file; | ||
219 | |||
220 | /// User's configuration file directory. | ||
221 | /// Set it with `Algorithm::SetConfigDirectory` | ||
222 | std::string o_user_config_dir; | ||
223 | |||
224 | /// A mutex for this algorithm | ||
225 | mutable std::mutex m_mutex; | ||
226 | |||
227 | private: // members | ||
228 | |||
229 | /// YAML reader | ||
230 | std::unique_ptr<YAMLReader> m_yaml_config; | ||
231 | |||
232 | /// Data structure to hold configuration options set by `Algorithm::SetOption` | ||
233 | std::unordered_map<std::string, option_t> m_option_cache; | ||
234 | |||
235 | }; | ||
236 | |||
237 | ////////////////////////////////////////////////////////////////////////////// | ||
238 | |||
239 | /// Algorithm pointer type | ||
240 | using algo_t = std::unique_ptr<Algorithm>; | ||
241 | |||
242 | /// @brief Factory to create an algorithm. | ||
243 | class AlgorithmFactory | ||
244 | { | ||
245 | |||
246 | public: | ||
247 | |||
248 | /// Algorithm creator function type | ||
249 | using algo_creator_t = std::function<algo_t()>; | ||
250 | |||
251 | AlgorithmFactory() = delete; | ||
252 | |||
253 | /// Register an algorithm with a unique name. Algorithms register themselves by calling this function. | ||
254 | /// @param name the name of the algorithm (not equivalent to `Object::m_name`) | ||
255 | /// @param creator the creator function | ||
256 | /// @param new_banks if this algorithm creates *new* banks, list them here | ||
257 | /// @returns true if the algorithm has not yet been registered | ||
258 | static bool Register(std::string const& name, algo_creator_t creator, std::vector<std::string> const new_banks = {}) noexcept; | ||
259 | |||
260 | /// Create an algorithm. Throws an exception if the algorithm cannot be created | ||
261 | /// @param name the name of the algorithm, which was used as an argument in the `AlgorithmFactory::Register` call | ||
262 | /// @returns the algorithm instance | ||
263 | static algo_t Create(std::string const& name) noexcept(false); | ||
264 | |||
265 | /// Check if a bank is created by an algorithm | ||
266 | /// @param bank_name the name of the bank | ||
267 | /// @returns the list of algorithms which create it, if any | ||
268 | static std::optional<std::vector<std::string>> QueryNewBank(std::string const& bank_name) noexcept; | ||
269 | |||
270 | private: | ||
271 | |||
272 | /// Association between the algorithm names and their creators | ||
273 | static std::unordered_map<std::string, algo_creator_t> s_creators; | ||
274 | |||
275 | /// Association between a created bank and its creator algorithms | ||
276 | static std::unordered_map<std::string, std::vector<std::string>> s_created_banks; | ||
277 | }; | ||
278 | } | ||
279 |