MaCh3 2.2.1
Reference Guide
Loading...
Searching...
No Matches
YamlHelper.h
Go to the documentation of this file.
1#pragma once
2
3// C++ Includes
4#include <iostream>
5#include <fstream>
6#include <string>
7#include <cxxabi.h>
8#include <regex>
9
10// MaCh3 Includes
12#include "Manager/Core.h"
13
15// ROOT Includes
16#include "TMacro.h"
17#include "TList.h"
18#include "TObjString.h"
19
20// yaml Includes
21#include "yaml-cpp/yaml.h"
23
28
29// **********************
32template<typename T>
33bool CheckNodeExistsHelper(const T& node) {
34// **********************
35 (void)node;
36 return true; // Node exists, return true
37}
38
39// **********************
41template<typename T, typename... Args>
42bool CheckNodeExistsHelper(const T& node, const std::string& key, Args... args) {\
43// **********************
44 if (!node[key]) {
45 //std::cerr << "Node " << key << " doesn't exist." << std::endl;
46 return false;
47 }
48 return CheckNodeExistsHelper(node[key], args...);
49}
50
51// **********************
53template<typename... Args>
54bool CheckNodeExists(const YAML::Node& node, Args... args) {
55// **********************
56 return CheckNodeExistsHelper(node, args...);
57}
58
59// **********************
62template<typename T>
63T FindFromManagerHelper(const YAML::Node& node) {
64// **********************
65 return node.as<T>(); // Convert YAML node to the specified type
66}
67
68// **********************
70template<typename T, typename... Args>
71T FindFromManagerHelper(const YAML::Node& node, const std::string& key, Args... args) {
72 // **********************
73 if (!node[key]) {
74 MACH3LOG_ERROR("Node {} doesn't exist.", key);
75 throw;
76 return T();
77 }
78 return FindFromManagerHelper<T>(node[key], args...); // Recursive call
79}
80// **********************
82template<typename T, typename... Args>
83T FindFromManager(const YAML::Node& node, Args... args) {
84// **********************
85 return FindFromManagerHelper<T>(node, args...);
86}
87
88// **********************
91inline YAML::Node STRINGtoYAML(const std::string& yaml_string){
92// **********************
93 try {
94 return YAML::Load(yaml_string);
95 } catch (const YAML::ParserException& e) {
96 MACH3LOG_ERROR("Error parsing YAML string: {}", e.what());
97 return YAML::Node();
98 }
99}
100
101// **********************
106inline std::string YAMLtoSTRING(const YAML::Node& node) {
107// **********************
108 YAML::Emitter emitter;
109 emitter << node;
110 return emitter.c_str();
111}
112
113// **********************
117inline std::string TMacroToString(const TMacro& macro) {
118// **********************
119 std::stringstream ss;
120
121 // Retrieve lines from TMacro
122 TList* linesList = macro.GetListOfLines();
123 if (!linesList) {
124 MACH3LOG_ERROR("Failed to retrieve lines from TMacro.");
125 return "";
126 }
127
128 TIter nextLine(linesList);
129 TObject *obj = nullptr;
130 while ((obj = nextLine())) {
131 TObjString* line = dynamic_cast<TObjString*>(obj);
132 if (!line) {
133 MACH3LOG_ERROR("Failed to cast object to TObjString.");
134 continue;
135 }
136 ss << line->GetString() << std::endl;
137 }
138
139 return ss.str();
140}
141
142// **********************
146inline YAML::Node TMacroToYAML(const TMacro& macro) {
147// **********************
148 std::string yaml_string = TMacroToString(macro);
149
150 // Convert the YAML string to a YAML node
151 YAML::Node yaml_node = STRINGtoYAML(yaml_string);
152
153 return yaml_node;
154}
155
156// **********************
161inline TMacro YAMLtoTMacro(const YAML::Node& yaml_node, const std::string& name) {
162// **********************
163 // Convert the YAML node to a string representation
164 std::string macro_string = YAMLtoSTRING(yaml_node);
165
166 // Create a TMacro object with the collected lines
167 TMacro macro(name.c_str(), name.c_str());
168 macro.AddLine(macro_string.c_str());
169
170 return macro;
171}
172
173// **********************
179inline bool compareYAMLNodes(const YAML::Node& node1, const YAML::Node& node2) {
180// **********************
181 // Check if the types of the nodes match
182 if (node1.Type() != node2.Type()) {
183 return false;
184 }
185
186 // Compare scalar types (like strings, numbers)
187 if (node1.IsScalar() && node2.IsScalar()) {
188 return node1.as<std::string>() == node2.as<std::string>();
189 }
190
191 // Compare sequences (like YAML lists)
192 if (node1.IsSequence() && node2.IsSequence()) {
193 if (node1.size() != node2.size()) {
194 return false;
195 }
196 for (std::size_t i = 0; i < node1.size(); ++i) {
197 if (!compareYAMLNodes(node1[i], node2[i])) {
198 return false;
199 }
200 }
201 return true;
202 }
203
204 // Compare maps (like YAML dictionaries)
205 if (node1.IsMap() && node2.IsMap()) {
206 if (node1.size() != node2.size()) {
207 return false;
208 }
209 for (auto it1 = node1.begin(); it1 != node1.end(); ++it1) {
210 auto key = it1->first.as<std::string>();
211 if (!node2[key] || !compareYAMLNodes(it1->second, node2[key])) {
212 return false;
213 }
214 }
215 return true;
216 }
217
218 // Default case: if it's neither scalar, sequence, nor map, consider it unequal
219 return false;
220}
221
222// **********************
225template <typename TValue>
226void OverrideConfig(YAML::Node node, std::string const &key, TValue val) {
227// **********************
228 node[key] = val;
229}
230
231// **********************
244template <typename... Args>
245void OverrideConfig(YAML::Node node, std::string const &key, Args... args) {
246// **********************
247 OverrideConfig(node[key], args...);
248}
249
250// **********************
252inline std::string DemangleTypeName(const std::string& mangledName) {
253// **********************
254 int status = 0;
255 char* demangledName = abi::__cxa_demangle(mangledName.c_str(), nullptr, nullptr, &status);
256 std::string result = (status == 0) ? demangledName : mangledName;
257 free(demangledName);
258 return result;
259}
260
261// **********************
264template<typename Type>
265Type Get(const YAML::Node& node, const std::string File, const int Line) {
266// **********************
267 try {
268 // Attempt to convert the node to the expected type
269 return node.as<Type>();
270 } catch (const YAML::BadConversion& e) {
271 const std::string nodeAsString = YAMLtoSTRING(node);
272 MACH3LOG_ERROR("YAML type mismatch: {}", e.what());
273 MACH3LOG_ERROR("While trying to access variable {}", nodeAsString);
274 throw MaCh3Exception(File , Line );
275 } catch (const YAML::InvalidNode& e) {
276 std::string key = "<unknown>";
277 const std::string msg = e.what();
278 const std::string search = "first invalid key: \"";
279 auto start = msg.find(search);
280 if (start != std::string::npos) {
281 start += search.length();
282 auto end = msg.find("\"", start);
283 if (end != std::string::npos) {
284 key = msg.substr(start, end - start);
285 }
286 }
287 MACH3LOG_ERROR("Invalid YAML node: {}", e.what());
288 MACH3LOG_ERROR("While trying to access key: {}", key);
289 throw MaCh3Exception(File , Line );
290 }
291}
292
293// **********************
297template<typename Type>
298Type GetFromManager(const YAML::Node& node, Type defval, const std::string File = "", const int Line = 1) {
299// **********************
300 if (!node) {
301 return defval;
302 }
303 try {
304 // Attempt to convert the node to the expected type
305 return node.as<Type>();
306 } catch (const YAML::BadConversion& e) {
307 const std::string nodeAsString = YAMLtoSTRING(node);
308 MACH3LOG_ERROR("YAML type mismatch: {}", e.what());
309 MACH3LOG_ERROR("While trying to access variable {}", nodeAsString);
310 //const std::string expectedType = DemangleTypeName(typeid(Type).name());
311 //MACH3LOG_ERROR("Expected argument is {}", expectedType);
312 if(File == "") {
313 throw MaCh3Exception(__FILE__ , __LINE__);
314 } else {
315 throw MaCh3Exception(File , Line );
316 }
317 }
318}
319
320// **********************
325inline YAML::Node LoadYamlConfig(const std::string& filename, const std::string& File, const int Line) {
326// **********************
327 // KS: YAML can be dumb and not throw error if you pass toml for example...
328 if (!(filename.length() >= 5 && filename.compare(filename.length() - 5, 5, ".yaml") == 0) &&
329 !(filename.length() >= 4 && filename.compare(filename.length() - 4, 4, ".yml") == 0)) {
330 MACH3LOG_ERROR("Invalid file extension: {}\n", filename);
331 throw MaCh3Exception(File, Line);
332 }
333
334 try {
335 YAML::Node yamlNode = YAML::LoadFile(filename);
336
337 // Convert the YAML file to string
338 std::ifstream fileStream(filename);
339 std::stringstream buffer;
340 buffer << fileStream.rdbuf();
341 std::string fileContent = buffer.str();
342
343 // Use regex to find any line starting with `-` but not followed by space, digit, or dot
344 // This excludes valid numeric negative values like -1760.0
345 std::regex linePattern(R"(^\s*-(?![\d\s\.]).*)");
346
347 std::istringstream contentStream(fileContent);
348 std::string lineconfig;
349 int lineNumber = 1;
350
351 // KS: Instead of boolean, track flow-style list nesting depth
352 int flowListDepth = 0;
353
354 while (std::getline(contentStream, lineconfig)) {
355 // Ignore YAML document separator
356 if (lineconfig.find("---") != std::string::npos) {
357 lineNumber++;
358 continue;
359 }
360
361 // Update flow list depth (increment for each '[' and decrement for each ']')
362 for (char c : lineconfig) {
363 if (c == '[') flowListDepth++;
364 else if (c == ']') flowListDepth = std::max(0, flowListDepth - 1);
365 }
366
367 // Check lines only outside flow-style lists
368 if (flowListDepth == 0 && std::regex_match(lineconfig, linePattern)) {
369 MACH3LOG_ERROR("Warning: Missing space or tab after '-' at line {}: {}\n", lineNumber, lineconfig);
370 throw MaCh3Exception(File, Line);
371 }
372
373 lineNumber++;
374 }
375
376 return yamlNode;
377
378 } catch (const std::exception& e) {
379 MACH3LOG_ERROR("{}\n", e.what());
380 MACH3LOG_ERROR("Can't open file {}\n", filename);
381 throw MaCh3Exception(File, Line);
382 }
383}
384
385// **********************
389inline const YAML::Node & Cnode(const YAML::Node &n) {
390// **********************
391 return n;
392}
393
394// **********************
405inline YAML::Node MergeNodes(YAML::Node a, YAML::Node b) {
406// **********************
407 if (!b.IsMap()) {
408 // If b is not a map, merge result is b, unless b is null
409 return b.IsNull() ? a : b;
410 }
411 if (!a.IsMap()) {
412 // If a is not a map, merge result is b
413 return b;
414 }
415 if (!b.size()) {
416 // If a is a map, and b is an empty map, return a
417 return a;
418 }
419 // Create a new map 'c' with the same mappings as a, merged with b
420 auto c = YAML::Node(YAML::NodeType::Map);
421 for (auto n : a) {
422 if (n.first.IsScalar()) {
423 const std::string & key = n.first.Scalar();
424 auto t = (Cnode(b)[key]);
425 if (t) {
426 c[n.first] = MergeNodes(n.second, t);
427 continue;
428 }
429 }
430 c[n.first] = n.second;
431 }
432 // Add the mappings from 'b' not already in 'c'
433 for (auto n : b) {
434 if (!n.first.IsScalar() || !Cnode(c)[n.first.Scalar()]) {
435 c[n.first] = n.second;
436 }
437 }
438 return c;
439}
440
441
442// **********************
454inline std::vector<double> ParseBounds(const YAML::Node& node, const std::string& File, const int Line) {
455// **********************
456 std::vector<double> bounds;
457
458 if (!node || !node.IsSequence() || (node.size() != 1 && node.size() != 2)) {
459 MACH3LOG_ERROR("Bounds must be a sequence of 1 or 2 elements, got size {}", static_cast<int>(node.size()));
460 throw MaCh3Exception(File, Line);
461 }
462
463 if (node.size() == 1) {
464 const YAML::Node& val = node[0];
465
466 if (!val || val.IsNull() || (val.IsScalar() && val.Scalar().empty())) {
467 MACH3LOG_ERROR("Single-element bounds must contain a valid numeric value.");
468 throw MaCh3Exception(File, Line);
469 } else if (val.IsScalar()) {
470 try {
471 const double center = val.as<double>();
472 if (std::floor(center) != center) {
473 MACH3LOG_ERROR("Invalid center value in Bounds[0]: '{}'. Expected an integer (e.g., 0 or 1).", static_cast<std::string>(val.Scalar()));
474 throw MaCh3Exception(File, Line);
475 }
476 bounds = {center - 0.5, center + 0.5};
477 } catch (const YAML::BadConversion& e) {
478 MACH3LOG_ERROR("Invalid value in Bounds[0]: '{}'. Expected a number.", static_cast<std::string>(val.Scalar()));
479 throw MaCh3Exception(File, Line);
480 }
481 } else {
482 MACH3LOG_ERROR("Invalid type in Bounds[0]");
483 throw MaCh3Exception(File, Line);
484 }
485 } else {
486 for (std::size_t i = 0; i < 2; ++i) {
487 const YAML::Node& val = node[i];
488
489 if (!val || val.IsNull() || (val.IsScalar() && val.Scalar().empty())) {
490 bounds.push_back(i == 0 ? M3::KinematicLowBound : M3::KinematicUpBound);
491 } else if (val.IsScalar()) {
492 try {
493 bounds.push_back(val.as<double>());
494 } catch (const YAML::BadConversion& e) {
495 MACH3LOG_ERROR("Invalid value in Bounds[{}]: '{}'. Expected a number or empty string.",
496 static_cast<int>(i), static_cast<std::string>(val.Scalar()));
497 throw MaCh3Exception(File, Line);
498 }
499 } else {
500 MACH3LOG_ERROR("Invalid type in Bounds[{}]", static_cast<int>(i));
501 throw MaCh3Exception(File, Line);
502 }
503 }
504 }
505 return bounds;
506}
507
508// **********************
538inline std::vector<std::vector<double>> Parse2DBounds(const YAML::Node& node, const std::string& File, const int Line) {
539// **********************
540 std::vector<std::vector<double>> bounds;
541 if (!node || !node.IsSequence()) {
542 MACH3LOG_ERROR("Expected a sequence node for 2D bounds");
543 throw MaCh3Exception(File, Line);
544 }
545
546 // Case 1: Single pair like [0.25, 0.5]
547 if (node.size() == 2 && node[0].IsScalar()) {
548 bounds.push_back(ParseBounds(node, File, Line));
549 } else { // Case 2: List of pairs like [[0.25, 0.5], [0.75, 1.0]]
550 for (std::size_t i = 0; i < node.size(); ++i) {
551 const YAML::Node& subnode = node[i];
552 bounds.push_back(ParseBounds(subnode, File, Line));
553 }
554 }
555
556 return bounds;
557}
558
560#define M3OpenConfig(filename) LoadYamlConfig((filename), __FILE__, __LINE__)
561#define GetBounds(filename) ParseBounds((filename), __FILE__, __LINE__)
562#define Get2DBounds(filename) Parse2DBounds((filename), __FILE__, __LINE__)
#define _MaCh3_Safe_Include_Start_
KS: Avoiding warning checking for headers.
Definition: Core.h:106
#define _MaCh3_Safe_Include_End_
KS: Restore warning checking after including external headers.
Definition: Core.h:117
#define MACH3LOG_ERROR
Definition: MaCh3Logger.h:25
Type GetFromManager(const YAML::Node &node, Type defval, const std::string File="", const int Line=1)
Get content of config file if node is not found take default value specified.
Definition: YamlHelper.h:298
void OverrideConfig(YAML::Node node, std::string const &key, TValue val)
Overrides the configuration settings based on provided arguments.
Definition: YamlHelper.h:226
T FindFromManagerHelper(const YAML::Node &node)
Definition: YamlHelper.h:63
T FindFromManager(const YAML::Node &node, Args... args)
Wrapper function to call the recursive helper.
Definition: YamlHelper.h:83
TMacro YAMLtoTMacro(const YAML::Node &yaml_node, const std::string &name)
Convert a YAML node to a ROOT TMacro object.
Definition: YamlHelper.h:161
bool CheckNodeExistsHelper(const T &node)
Use this like this CheckNodeExists(config, "LikelihoodOptions", "TestStatistic"); KS: Base case for r...
Definition: YamlHelper.h:33
const YAML::Node & Cnode(const YAML::Node &n)
KS: Convenience wrapper to return a YAML node as-is.
Definition: YamlHelper.h:389
YAML::Node MergeNodes(YAML::Node a, YAML::Node b)
KS: Recursively merges two YAML nodes.
Definition: YamlHelper.h:405
std::vector< double > ParseBounds(const YAML::Node &node, const std::string &File, const int Line)
KS: Get bounds from YAML for example for selection cuts.
Definition: YamlHelper.h:454
YAML::Node STRINGtoYAML(const std::string &yaml_string)
Function to convert a YAML string to a YAML node.
Definition: YamlHelper.h:91
std::string DemangleTypeName(const std::string &mangledName)
Function to demangle type names.
Definition: YamlHelper.h:252
YAML::Node LoadYamlConfig(const std::string &filename, const std::string &File, const int Line)
Open YAML file.
Definition: YamlHelper.h:325
YAML::Node TMacroToYAML(const TMacro &macro)
KS: Convert a ROOT TMacro object to a YAML node.
Definition: YamlHelper.h:146
std::string TMacroToString(const TMacro &macro)
KS: Convert a ROOT TMacro object to a string representation.
Definition: YamlHelper.h:117
std::string YAMLtoSTRING(const YAML::Node &node)
KS: Convert a YAML node to a string representation.
Definition: YamlHelper.h:106
bool compareYAMLNodes(const YAML::Node &node1, const YAML::Node &node2)
Compare if yaml nodes are identical.
Definition: YamlHelper.h:179
Type Get(const YAML::Node &node, const std::string File, const int Line)
Get content of config file.
Definition: YamlHelper.h:265
bool CheckNodeExists(const YAML::Node &node, Args... args)
KS: Wrapper function to call the recursive helper.
Definition: YamlHelper.h:54
std::vector< std::vector< double > > Parse2DBounds(const YAML::Node &node, const std::string &File, const int Line)
KS: Get 2D bounds from YAML for example for selection cuts.
Definition: YamlHelper.h:538
Custom exception class for MaCh3 errors.
static constexpr const double KinematicLowBound
When parameter has no bound this serves as it. Lowest possible value the system.
Definition: Core.h:65
static constexpr const double KinematicUpBound
When parameter has no bound this serves as it. Highest possible value the system.
Definition: Core.h:67