MaCh3  2.4.2
Reference Guide
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
11 #include "Manager/MaCh3Exception.h"
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 
34 
35 // **********************
38 template<typename T>
39 bool CheckNodeExistsHelper(const T& node) {
40 // **********************
41  (void)node;
42  return true; // Node exists, return true
43 }
44 
45 // **********************
47 template<typename T, typename... Args>
48 bool CheckNodeExistsHelper(const T& node, const std::string& key, Args... args) {\
49 // **********************
50  if (!node[key]) {
51  MACH3LOG_TRACE("Node '{}' doesn't exist.", key);
52  return false;
53  }
54  return CheckNodeExistsHelper(node[key], args...);
55 }
56 
57 // **********************
59 template<typename... Args>
60 bool CheckNodeExists(const YAML::Node& node, Args... args) {
61 // **********************
62  return CheckNodeExistsHelper(node, args...);
63 }
64 
65 // **********************
68 template<typename T>
69 T FindFromManagerHelper(const YAML::Node& node) {
70 // **********************
71  return node.as<T>(); // Convert YAML node to the specified type
72 }
73 
74 // **********************
76 template<typename T, typename... Args>
77 T FindFromManagerHelper(const YAML::Node& node, const std::string& key, Args... args) {
78 // **********************
79  if (!node[key]) {
80  MACH3LOG_ERROR("Node {} doesn't exist.", key);
81  throw MaCh3Exception(__FILE__, __LINE__);
82  return T();
83  }
84  return FindFromManagerHelper<T>(node[key], args...); // Recursive call
85 }
86 // **********************
88 template<typename T, typename... Args>
89 T FindFromManager(const YAML::Node& node, Args... args) {
90 // **********************
91  return FindFromManagerHelper<T>(node, args...);
92 }
93 
94 // **********************
97 inline YAML::Node STRINGtoYAML(const std::string& yaml_string){
98 // **********************
99  try {
100  return YAML::Load(yaml_string);
101  } catch (const YAML::ParserException& e) {
102  MACH3LOG_ERROR("Error parsing YAML string: {}", e.what());
103  return YAML::Node();
104  }
105 }
106 
107 // **********************
112 inline std::string YAMLtoSTRING(const YAML::Node& node) {
113 // **********************
114  YAML::Emitter emitter;
115  emitter << node;
116  return emitter.c_str();
117 }
118 
119 // **********************
123 inline std::string TMacroToString(const TMacro& macro) {
124 // **********************
125  std::stringstream ss;
126 
127  // Retrieve lines from TMacro
128  TList* linesList = macro.GetListOfLines();
129  if (!linesList) {
130  MACH3LOG_ERROR("Failed to retrieve lines from TMacro.");
131  return "";
132  }
133 
134  TIter nextLine(linesList);
135  TObject *obj = nullptr;
136  while ((obj = nextLine())) {
137  TObjString* line = dynamic_cast<TObjString*>(obj);
138  if (!line) {
139  MACH3LOG_ERROR("Failed to cast object to TObjString.");
140  continue;
141  }
142  ss << line->GetString() << std::endl;
143  }
144 
145  return ss.str();
146 }
147 
148 // **********************
152 inline YAML::Node TMacroToYAML(const TMacro& macro) {
153 // **********************
154  std::string yaml_string = TMacroToString(macro);
155 
156  // Convert the YAML string to a YAML node
157  YAML::Node yaml_node = STRINGtoYAML(yaml_string);
158 
159  return yaml_node;
160 }
161 
162 // **********************
167 inline TMacro YAMLtoTMacro(const YAML::Node& yaml_node, const std::string& name) {
168 // **********************
169  // Convert the YAML node to a string representation
170  std::string macro_string = YAMLtoSTRING(yaml_node);
171 
172  // Create a TMacro object with the collected lines
173  TMacro macro(name.c_str(), name.c_str());
174  macro.AddLine(macro_string.c_str());
175 
176  return macro;
177 }
178 
179 // **********************
186 inline bool compareYAMLNodes(const YAML::Node& node1, const YAML::Node& node2, bool Mute = false) {
187 // **********************
188  // Check if the types of the nodes match
189  if (node1.Type() != node2.Type()) {
190  return false;
191  }
192 
193  // Compare scalar types (like strings, numbers)
194  if (node1.IsScalar() && node2.IsScalar()) {
195  auto v1 = node1.as<std::string>();
196  auto v2 = node2.as<std::string>();
197  if (v1 != v2) {
198  if (!Mute) MACH3LOG_WARN("Scalar value mismatch: '{}' != '{}'", v1, v2);
199  return false;
200  }
201  return true;
202  }
203 
204  // Compare sequences (like YAML lists)
205  if (node1.IsSequence() && node2.IsSequence()) {
206  if (node1.size() != node2.size()) {
207  return false;
208  }
209  for (std::size_t i = 0; i < node1.size(); ++i) {
210  if (!compareYAMLNodes(node1[i], node2[i])) {
211  return false;
212  }
213  }
214  return true;
215  }
216 
217  // Compare maps (like YAML dictionaries)
218  if (node1.IsMap() && node2.IsMap()) {
219  if (node1.size() != node2.size()) {
220  MACH3LOG_WARN("Map size mismatch: {} != {}",
221  node1.size(), node2.size());
222  return false;
223  }
224  for (auto it1 = node1.begin(); it1 != node1.end(); ++it1) {
225  auto key = it1->first.as<std::string>();
226  if (!node2[key]) {
227  MACH3LOG_WARN("Key '{}' missing in second YAML", key);
228  return false;
229  }
230  if (!compareYAMLNodes(it1->second, node2[key])) {
231  MACH3LOG_WARN("Value mismatch at key '{}'", key);
232  return false;
233  }
234  }
235  return true;
236  }
237 
238  // Default case: if it's neither scalar, sequence, nor map, consider it unequal
239  if (!Mute) MACH3LOG_WARN("Unsupported YAML node type encountered");
240  return false;
241 }
242 
243 // **********************
248 template <typename TValue>
249 void OverrideConfig(YAML::Node node, std::string const &key, TValue val) {
250 // **********************
251  node[key] = val;
252 }
253 
254 // **********************
268 template <typename... Args>
269 void OverrideConfig(YAML::Node node, std::string const &key, Args... args) {
270 // **********************
271  OverrideConfig(node[key], args...);
272 }
273 
274 // **********************
276 inline std::string DemangleTypeName(const std::string& mangledName) {
277 // **********************
278  int status = 0;
279  char* demangledName = abi::__cxa_demangle(mangledName.c_str(), nullptr, nullptr, &status);
280  std::string result = (status == 0) ? demangledName : mangledName;
281  free(demangledName);
282  return result;
283 }
284 
285 // **********************
290 template<typename Type>
291 Type Get(const YAML::Node& node, const std::string File, const int Line) {
292 // **********************
293  try {
294  // Attempt to convert the node to the expected type
295  return node.as<Type>();
296  } catch (const YAML::BadConversion& e) {
297  const std::string nodeAsString = YAMLtoSTRING(node);
298  MACH3LOG_ERROR("YAML type mismatch: {}", e.what());
299  MACH3LOG_ERROR("While trying to access variable \"{}\"", nodeAsString);
300  if (!node || node.IsNull()) {
301  MACH3LOG_ERROR("Requested node does not exist (null or undefined)");
302  }
303  throw MaCh3Exception(File , Line );
304  } catch (const YAML::InvalidNode& e) {
305  std::string key = "<unknown>";
306  const std::string msg = e.what();
307  const std::string search = "first invalid key: \"";
308  auto start = msg.find(search);
309  if (start != std::string::npos) {
310  start += search.length();
311  auto end = msg.find("\"", start);
312  if (end != std::string::npos) {
313  key = msg.substr(start, end - start);
314  }
315  }
316  MACH3LOG_ERROR("Invalid YAML node: {}", e.what());
317  MACH3LOG_ERROR("While trying to access key: {}", key);
318  throw MaCh3Exception(File , Line );
319  }
320 }
321 
322 // **********************
328 template<typename Type>
329 Type GetFromManager(const YAML::Node& node, const Type defval, const std::string File = "", const int Line = 1) {
330 // **********************
331  if (!node) {
332  return defval;
333  }
334  try {
335  // Attempt to convert the node to the expected type
336  return node.as<Type>();
337  } catch (const YAML::BadConversion& e) {
338  const std::string nodeAsString = YAMLtoSTRING(node);
339  MACH3LOG_ERROR("YAML type mismatch: {}", e.what());
340  MACH3LOG_ERROR("While trying to access variable {}", nodeAsString);
341  //const std::string expectedType = DemangleTypeName(typeid(Type).name());
342  //MACH3LOG_ERROR("Expected argument is {}", expectedType);
343  if(File == "") {
344  throw MaCh3Exception(__FILE__ , __LINE__);
345  } else {
346  throw MaCh3Exception(File , Line );
347  }
348  }
349 }
350 
351 // **********************
356 inline YAML::Node LoadYamlConfig(const std::string& filename, const std::string& File, const int Line) {
357 // **********************
358  // KS: YAML can be dumb and not throw error if you pass toml for example...
359  if (!(filename.length() >= 5 && filename.compare(filename.length() - 5, 5, ".yaml") == 0) &&
360  !(filename.length() >= 4 && filename.compare(filename.length() - 4, 4, ".yml") == 0)) {
361  MACH3LOG_ERROR("Invalid file extension: {}\n", filename);
362  throw MaCh3Exception(File, Line);
363  }
364 
365  try {
366  YAML::Node yamlNode = YAML::LoadFile(filename);
367 
368  // Convert the YAML file to string
369  std::ifstream fileStream(filename);
370  std::stringstream buffer;
371  buffer << fileStream.rdbuf();
372  std::string fileContent = buffer.str();
373 
374  // Use regex to find any line starting with `-` but not followed by space, digit, or dot
375  // This excludes valid numeric negative values like -1760.0
376  std::regex linePattern(R"(^\s*-(?![\d\s\.]).*)");
377 
378  std::istringstream contentStream(fileContent);
379  std::string lineconfig;
380  int lineNumber = 1;
381 
382  // KS: Instead of boolean, track flow-style list nesting depth
383  int flowListDepth = 0;
384 
385  while (std::getline(contentStream, lineconfig)) {
386  // Ignore YAML document separator
387  if (lineconfig.find("---") != std::string::npos) {
388  lineNumber++;
389  continue;
390  }
391 
392  // Update flow list depth (increment for each '[' and decrement for each ']')
393  for (char c : lineconfig) {
394  if (c == '[') flowListDepth++;
395  else if (c == ']') flowListDepth = std::max(0, flowListDepth - 1);
396  }
397 
398  // Check lines only outside flow-style lists
399  if (flowListDepth == 0 && std::regex_match(lineconfig, linePattern)) {
400  MACH3LOG_ERROR("Warning: Missing space or tab after '-' at line {}: {}\n", lineNumber, lineconfig);
401  throw MaCh3Exception(File, Line);
402  }
403  lineNumber++;
404  }
405 
406  return yamlNode;
407  } catch (const std::exception& e) {
408  MACH3LOG_ERROR("{}\n", e.what());
409  MACH3LOG_ERROR("Can't open file {}\n", filename);
410  throw MaCh3Exception(File, Line);
411  }
412 }
413 
414 // **********************
418 inline const YAML::Node & Cnode(const YAML::Node &n) {
419 // **********************
420  return n;
421 }
422 
423 // **********************
434 inline YAML::Node MergeNodes(const YAML::Node& a, const YAML::Node& b) {
435 // **********************
436  if (!b.IsMap()) {
437  // If b is not a map, merge result is b, unless b is null
438  return b.IsNull() ? a : b;
439  }
440  if (!a.IsMap()) {
441  // If a is not a map, merge result is b
442  return b;
443  }
444  if (!b.size()) {
445  // If a is a map, and b is an empty map, return a
446  return a;
447  }
448  // Create a new map 'c' with the same mappings as a, merged with b
449  auto c = YAML::Node(YAML::NodeType::Map);
450  for (auto n : a) {
451  if (n.first.IsScalar()) {
452  const std::string & key = n.first.Scalar();
453  auto t = (Cnode(b)[key]);
454  if (t) {
455  c[n.first] = MergeNodes(n.second, t);
456  continue;
457  }
458  }
459  c[n.first] = n.second;
460  }
461  // Add the mappings from 'b' not already in 'c'
462  for (auto n : b) {
463  if (!n.first.IsScalar() || !Cnode(c)[n.first.Scalar()]) {
464  c[n.first] = n.second;
465  }
466  }
467  return c;
468 }
469 
470 
471 // **********************
483 inline std::vector<double> ParseBounds(const YAML::Node& node, const std::string& File, const int Line) {
484 // **********************
485  std::vector<double> bounds;
486 
487  if (!node || !node.IsSequence() || (node.size() != 1 && node.size() != 2)) {
488  MACH3LOG_ERROR("Bounds must be a sequence of 1 or 2 elements, got size {}", static_cast<int>(node.size()));
489  throw MaCh3Exception(File, Line);
490  }
491 
492  if (node.size() == 1) {
493  const YAML::Node& val = node[0];
494 
495  if (!val || val.IsNull() || (val.IsScalar() && val.Scalar().empty())) {
496  MACH3LOG_ERROR("Single-element bounds must contain a valid numeric value.");
497  throw MaCh3Exception(File, Line);
498  } else if (val.IsScalar()) {
499  try {
500  const double center = val.as<double>();
501  if (std::floor(center) != center) {
502  MACH3LOG_ERROR("Invalid center value in Bounds[0]: '{}'. Expected an integer (e.g., 0 or 1).", static_cast<std::string>(val.Scalar()));
503  throw MaCh3Exception(File, Line);
504  }
505  bounds = {center - 0.5, center + 0.5};
506  } catch (const YAML::BadConversion& e) {
507  MACH3LOG_ERROR("Invalid value in Bounds[0]: '{}'. Expected a number.", static_cast<std::string>(val.Scalar()));
508  throw MaCh3Exception(File, Line);
509  }
510  } else {
511  MACH3LOG_ERROR("Invalid type in Bounds[0]");
512  throw MaCh3Exception(File, Line);
513  }
514  } else {
515  for (std::size_t i = 0; i < 2; ++i) {
516  const YAML::Node& val = node[i];
517 
518  if (!val || val.IsNull() || (val.IsScalar() && val.Scalar().empty())) {
519  bounds.push_back(i == 0 ? M3::KinematicLowBound : M3::KinematicUpBound);
520  } else if (val.IsScalar()) {
521  try {
522  bounds.push_back(val.as<double>());
523  } catch (const YAML::BadConversion& e) {
524  MACH3LOG_ERROR("Invalid value in Bounds[{}]: '{}'. Expected a number or empty string.",
525  static_cast<int>(i), static_cast<std::string>(val.Scalar()));
526  throw MaCh3Exception(File, Line);
527  }
528  } else {
529  MACH3LOG_ERROR("Invalid type in Bounds[{}]", static_cast<int>(i));
530  throw MaCh3Exception(File, Line);
531  }
532  }
533  }
534  return bounds;
535 }
536 
537 // **********************
567 inline std::vector<std::vector<double>> Parse2DBounds(const YAML::Node& node, const std::string& File, const int Line) {
568 // **********************
569  std::vector<std::vector<double>> bounds;
570  if (!node || !node.IsSequence()) {
571  MACH3LOG_ERROR("Expected a sequence node for 2D bounds");
572  throw MaCh3Exception(File, Line);
573  }
574 
575  // Case 1: Single pair like [0.25, 0.5]
576  if (node.size() == 2 && node[0].IsScalar()) {
577  bounds.push_back(ParseBounds(node, File, Line));
578  } else { // Case 2: List of pairs like [[0.25, 0.5], [0.75, 1.0]]
579  for (std::size_t i = 0; i < node.size(); ++i) {
580  const YAML::Node& subnode = node[i];
581  bounds.push_back(ParseBounds(subnode, File, Line));
582  }
583  }
584 
585  return bounds;
586 }
587 
589 #define M3OpenConfig(filename) LoadYamlConfig((filename), __FILE__, __LINE__)
590 #define GetBounds(filename) ParseBounds((filename), __FILE__, __LINE__)
591 #define Get2DBounds(filename) Parse2DBounds((filename), __FILE__, __LINE__)
KS: Core MaCh3 definitions and compile-time configuration utilities.
#define _MaCh3_Safe_Include_Start_
KS: Avoiding warning checking for headers.
Definition: Core.h:126
#define _MaCh3_Safe_Include_End_
KS: Restore warning checking after including external headers.
Definition: Core.h:140
Defines the custom exception class used throughout MaCh3.
#define MACH3LOG_ERROR
Definition: MaCh3Logger.h:37
#define MACH3LOG_WARN
Definition: MaCh3Logger.h:36
#define MACH3LOG_TRACE
Definition: MaCh3Logger.h:33
void OverrideConfig(YAML::Node node, std::string const &key, TValue val)
Overrides the configuration settings based on provided arguments.
Definition: YamlHelper.h:249
T FindFromManagerHelper(const YAML::Node &node)
Definition: YamlHelper.h:69
T FindFromManager(const YAML::Node &node, Args... args)
Wrapper function to call the recursive helper.
Definition: YamlHelper.h:89
TMacro YAMLtoTMacro(const YAML::Node &yaml_node, const std::string &name)
Convert a YAML node to a ROOT TMacro object.
Definition: YamlHelper.h:167
bool CheckNodeExistsHelper(const T &node)
Use this like this CheckNodeExists(config, "LikelihoodOptions", "TestStatistic"); KS: Base case for r...
Definition: YamlHelper.h:39
YAML::Node STRINGtoYAML(const std::string &yaml_string)
Function to convert a YAML string to a YAML node.
Definition: YamlHelper.h:97
std::string DemangleTypeName(const std::string &mangledName)
Function to demangle type names.
Definition: YamlHelper.h:276
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:483
YAML::Node LoadYamlConfig(const std::string &filename, const std::string &File, const int Line)
Open YAML file.
Definition: YamlHelper.h:356
YAML::Node TMacroToYAML(const TMacro &macro)
KS: Convert a ROOT TMacro object to a YAML node.
Definition: YamlHelper.h:152
Type GetFromManager(const YAML::Node &node, const 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:329
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:567
std::string TMacroToString(const TMacro &macro)
KS: Convert a ROOT TMacro object to a string representation.
Definition: YamlHelper.h:123
YAML::Node MergeNodes(const YAML::Node &a, const YAML::Node &b)
KS: Recursively merges two YAML nodes.
Definition: YamlHelper.h:434
std::string YAMLtoSTRING(const YAML::Node &node)
KS: Convert a YAML node to a string representation.
Definition: YamlHelper.h:112
Type Get(const YAML::Node &node, const std::string File, const int Line)
Get content of config file.
Definition: YamlHelper.h:291
bool compareYAMLNodes(const YAML::Node &node1, const YAML::Node &node2, bool Mute=false)
Compare if yaml nodes are identical.
Definition: YamlHelper.h:186
bool CheckNodeExists(const YAML::Node &node, Args... args)
KS: Wrapper function to call the recursive helper.
Definition: YamlHelper.h:60
const YAML::Node & Cnode(const YAML::Node &n)
KS: Convenience wrapper to return a YAML node as-is.
Definition: YamlHelper.h:418
Custom exception class used throughout MaCh3.
constexpr static const double KinematicLowBound
When parameter has no bound this serves as it. Lowest possible value the system.
Definition: Core.h:75
constexpr static const double KinematicUpBound
When parameter has no bound this serves as it. Highest possible value the system.
Definition: Core.h:77