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