MaCh3  2.8.0
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([[maybe_unused]] const T& node) {
40 // **********************
41  return true; // Node exists, return true
42 }
43 
44 // **********************
46 template<typename T, typename... Args>
47 bool CheckNodeExistsHelper(const T& node, const std::string& key, Args... args) {\
48 // **********************
49  if (!node[key]) {
50  MACH3LOG_TRACE("Node '{}' doesn't exist.", key);
51  return false;
52  }
53  return CheckNodeExistsHelper(node[key], args...);
54 }
55 
56 // **********************
58 template<typename... Args>
59 bool CheckNodeExists(const YAML::Node& node, Args... args) {
60 // **********************
61  return CheckNodeExistsHelper(node, args...);
62 }
63 
64 // **********************
67 template<typename T>
68 T FindFromManagerHelper(const YAML::Node& node) {
69 // **********************
70  return node.as<T>(); // Convert YAML node to the specified type
71 }
72 
73 // **********************
75 template<typename T, typename... Args>
76 T FindFromManagerHelper(const YAML::Node& node, const std::string& key, Args... args) {
77 // **********************
78  if (!node[key]) {
79  MACH3LOG_ERROR("Node {} doesn't exist.", key);
80  throw MaCh3Exception(__FILE__, __LINE__);
81  return T();
82  }
83  return FindFromManagerHelper<T>(node[key], args...); // Recursive call
84 }
85 // **********************
87 template<typename T, typename... Args>
88 T FindFromManager(const YAML::Node& node, Args... args) {
89 // **********************
90  return FindFromManagerHelper<T>(node, args...);
91 }
92 
93 // **********************
96 inline YAML::Node STRINGtoYAML(const std::string& yaml_string){
97 // **********************
98  try {
99  return YAML::Load(yaml_string);
100  } catch (const YAML::ParserException& e) {
101  MACH3LOG_ERROR("Error parsing YAML string: {}", e.what());
102  return YAML::Node();
103  }
104 }
105 
106 // **********************
111 inline std::string YAMLtoSTRING(const YAML::Node& node) {
112 // **********************
113  YAML::Emitter emitter;
114  emitter << node;
115  return emitter.c_str();
116 }
117 
118 // **********************
122 inline std::string TMacroToString(const TMacro& macro) {
123 // **********************
124  std::stringstream ss;
125 
126  // Retrieve lines from TMacro
127  TList* linesList = macro.GetListOfLines();
128  if (!linesList) {
129  MACH3LOG_ERROR("Failed to retrieve lines from TMacro.");
130  return "";
131  }
132 
133  TIter nextLine(linesList);
134  TObject *obj = nullptr;
135  while ((obj = nextLine())) {
136  TObjString* line = dynamic_cast<TObjString*>(obj);
137  if (!line) {
138  MACH3LOG_ERROR("Failed to cast object to TObjString.");
139  continue;
140  }
141  ss << line->GetString() << std::endl;
142  }
143 
144  return ss.str();
145 }
146 
147 // **********************
151 inline YAML::Node TMacroToYAML(const TMacro& macro) {
152 // **********************
153  std::string yaml_string = TMacroToString(macro);
154 
155  // Convert the YAML string to a YAML node
156  YAML::Node yaml_node = STRINGtoYAML(yaml_string);
157 
158  return yaml_node;
159 }
160 
161 // **********************
166 inline TMacro YAMLtoTMacro(const YAML::Node& yaml_node, const std::string& name) {
167 // **********************
168  // Convert the YAML node to a string representation
169  std::string macro_string = YAMLtoSTRING(yaml_node);
170 
171  // Create a TMacro object with the collected lines
172  TMacro macro(name.c_str(), name.c_str());
173  macro.AddLine(macro_string.c_str());
174 
175  return macro;
176 }
177 
178 // **********************
185 inline bool compareYAMLNodes(const YAML::Node& node1, const YAML::Node& node2, bool Mute = false) {
186 // **********************
187  // Check if the types of the nodes match
188  if (node1.Type() != node2.Type()) {
189  return false;
190  }
191 
192  // Compare scalar types (like strings, numbers)
193  if (node1.IsScalar() && node2.IsScalar()) {
194  auto v1 = node1.as<std::string>();
195  auto v2 = node2.as<std::string>();
196  if (v1 != v2) {
197  if (!Mute) MACH3LOG_WARN("Scalar value mismatch: '{}' != '{}'", v1, v2);
198  return false;
199  }
200  return true;
201  }
202 
203  // Compare sequences (like YAML lists)
204  if (node1.IsSequence() && node2.IsSequence()) {
205  if (node1.size() != node2.size()) {
206  return false;
207  }
208  for (std::size_t i = 0; i < node1.size(); ++i) {
209  if (!compareYAMLNodes(node1[i], node2[i])) {
210  return false;
211  }
212  }
213  return true;
214  }
215 
216  // Compare maps (like YAML dictionaries)
217  if (node1.IsMap() && node2.IsMap()) {
218  if (node1.size() != node2.size()) {
219  MACH3LOG_WARN("Map size mismatch: {} != {}",
220  node1.size(), node2.size());
221  return false;
222  }
223  for (auto it1 = node1.begin(); it1 != node1.end(); ++it1) {
224  auto key = it1->first.as<std::string>();
225  if (!node2[key]) {
226  MACH3LOG_WARN("Key '{}' missing in second YAML", key);
227  return false;
228  }
229  if (!compareYAMLNodes(it1->second, node2[key])) {
230  MACH3LOG_WARN("Value mismatch at key '{}'", key);
231  return false;
232  }
233  }
234  return true;
235  }
236 
237  // Default case: if it's neither scalar, sequence, nor map, consider it unequal
238  if (!Mute) MACH3LOG_WARN("Unsupported YAML node type encountered");
239  return false;
240 }
241 
242 // **********************
247 template <typename TValue>
248 void OverrideConfig(YAML::Node node, std::string const &key, TValue val) {
249 // **********************
250  node[key] = val;
251 }
252 
253 // **********************
267 template <typename... Args>
268 void OverrideConfig(YAML::Node node, std::string const &key, Args... args) {
269 // **********************
270  OverrideConfig(node[key], args...);
271 }
272 
273 // **********************
275 inline std::string DemangleTypeName(const std::string& mangledName) {
276 // **********************
277  int status = 0;
278  char* demangledName = abi::__cxa_demangle(mangledName.c_str(), nullptr, nullptr, &status);
279  std::string result = (status == 0) ? demangledName : mangledName;
280  free(demangledName);
281  return result;
282 }
283 
284 // **********************
289 template<typename Type>
290 Type Get(const YAML::Node& node, const std::string& File, const int Line) {
291 // **********************
292  try {
293  // Attempt to convert the node to the expected type
294  return node.as<Type>();
295  } catch (const YAML::BadConversion& e) {
296  const std::string nodeAsString = YAMLtoSTRING(node);
297  MACH3LOG_ERROR("YAML type mismatch: {}", e.what());
298  MACH3LOG_ERROR("While trying to access variable \"{}\"", nodeAsString);
299  if (!node || node.IsNull()) {
300  MACH3LOG_ERROR("Requested node does not exist (null or undefined)");
301  }
302  throw MaCh3Exception(File , Line );
303  } catch (const YAML::InvalidNode& e) {
304  std::string key = "<unknown>";
305  const std::string msg = e.what();
306  const std::string search = "first invalid key: \"";
307  auto start = msg.find(search);
308  if (start != std::string::npos) {
309  start += search.length();
310  auto end = msg.find("\"", start);
311  if (end != std::string::npos) {
312  key = msg.substr(start, end - start);
313  }
314  }
315  MACH3LOG_ERROR("Invalid YAML node: {}", e.what());
316  MACH3LOG_ERROR("While trying to access key: {}", key);
317  throw MaCh3Exception(File , Line );
318  }
319 }
320 
321 // **********************
327 template<typename Type>
328 Type GetFromManager(const YAML::Node& node, const Type defval, const std::string& File, const int Line) {
329 // **********************
330  if (!node) {
331  return defval;
332  }
333  try {
334  // Attempt to convert the node to the expected type
335  return node.as<Type>();
336  } catch (const YAML::BadConversion& e) {
337  const std::string nodeAsString = YAMLtoSTRING(node);
338  MACH3LOG_ERROR("YAML type mismatch: {}", e.what());
339  MACH3LOG_ERROR("While trying to access variable {}", nodeAsString);
340  //const std::string expectedType = DemangleTypeName(typeid(Type).name());
341  //MACH3LOG_ERROR("Expected argument is {}", expectedType);
342  if(File == "") {
343  throw MaCh3Exception(__FILE__ , __LINE__);
344  } else {
345  throw MaCh3Exception(File , Line );
346  }
347  }
348 }
349 
350 // **********************
355 inline YAML::Node LoadYamlConfig(const std::string& filename, const std::string& File, const int Line) {
356 // **********************
357  // KS: YAML can be dumb and not throw error if you pass toml for example...
358  if (!(filename.length() >= 5 && filename.compare(filename.length() - 5, 5, ".yaml") == 0) &&
359  !(filename.length() >= 4 && filename.compare(filename.length() - 4, 4, ".yml") == 0)) {
360  MACH3LOG_ERROR("Invalid file extension: {}", filename);
361  MACH3LOG_ERROR("I was expecting .yaml or .yml");
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_
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:248
T FindFromManagerHelper(const YAML::Node &node)
Definition: YamlHelper.h:68
T FindFromManager(const YAML::Node &node, Args... args)
Wrapper function to call the recursive helper.
Definition: YamlHelper.h:88
TMacro YAMLtoTMacro(const YAML::Node &yaml_node, const std::string &name)
Convert a YAML node to a ROOT TMacro object.
Definition: YamlHelper.h:166
bool CheckNodeExistsHelper([[maybe_unused]] 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:96
std::string DemangleTypeName(const std::string &mangledName)
Function to demangle type names.
Definition: YamlHelper.h:275
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:355
YAML::Node TMacroToYAML(const TMacro &macro)
KS: Convert a ROOT TMacro object to a YAML node.
Definition: YamlHelper.h:151
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:122
Type GetFromManager(const YAML::Node &node, const Type defval, const std::string &File, const int Line)
Get content of config file if node is not found take default value specified.
Definition: YamlHelper.h:328
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:111
bool compareYAMLNodes(const YAML::Node &node1, const YAML::Node &node2, bool Mute=false)
Compare if yaml nodes are identical.
Definition: YamlHelper.h:185
Type Get(const YAML::Node &node, const std::string &File, const int Line)
Get content of config file.
Definition: YamlHelper.h:290
bool CheckNodeExists(const YAML::Node &node, Args... args)
KS: Wrapper function to call the recursive helper.
Definition: YamlHelper.h:59
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