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