MaCh3  2.8.0
Reference Guide
UmbrellaSolver.cpp
Go to the documentation of this file.
1 
6 #include <filesystem>
8 #include "Manager/Manager.h"
10 
12 #include "TSystem.h"
13 #include "TChain.h"
14 #include "TSystemDirectory.h"
16 
17 bool debug_mode = false;
20 struct WindowConfig {
21  std::string name;
23  double center;
24  double width;
25  std::string input_file;
28 };
29 
33  std::vector<WindowConfig> windows;
34  std::string output_file;
35  std::string variable_of_interest;
37  std::string dynamic_pattern;
40  double tolerance;
42 };
43 
45 UmbrellaConfig parseYAMLConfig(const std::string &filename) {
46  UmbrellaConfig config;
47 
48  try {
49  YAML::Node yaml_diag_config = YAML::LoadFile(filename);
50  YAML::Node yaml_config = yaml_diag_config["UmbrellaSolver"];
51 
52  // Parse other configuration
53  config.output_file = Get<std::string>(yaml_config["output_file"], __FILE__ , __LINE__);
54  config.max_iterations = Get<int>(yaml_config["max_iterations"], __FILE__ , __LINE__);
55  config.tolerance = Get<double>(yaml_config["tolerance"], __FILE__ , __LINE__);
56  config.print_frequency = GetFromManager<int>(yaml_config["print_frequency"], 0, __FILE__ , __LINE__);
57  config.dynamic_files = GetFromManager<bool>(yaml_config["dynamic_files"], false, __FILE__ , __LINE__);
58  config.dynamic_pattern = Get<std::string>(yaml_config["dynamic_pattern"], __FILE__ , __LINE__);
59  config.dynamic_n_windows = Get<int>(yaml_config["dynamic_n_windows"], __FILE__ , __LINE__);
60 
61  if (!config.dynamic_files) {
62  // TODO: these are mostly redundant now that the MaCh3_Config macro is
63  // being parsed, work out if its safe to remove them at somepoint!!! Parse
64  // windows
65  if (yaml_config["windows"]) {
66  const YAML::Node &windows = yaml_config["windows"];
67  for (size_t i = 0; i < windows.size(); i++) {
68  WindowConfig window;
69  window.name = Get<std::string>(windows[i]["name"], __FILE__, __LINE__);
70  window.center = Get<double>(windows[i]["center"], __FILE__, __LINE__);
71  window.width = Get<double>(windows[i]["width"], __FILE__, __LINE__);
72  config.windows.push_back(window);
73  }
74  }
75 
76  // Parse input files
77  if (yaml_config["input_files"]) {
78  const YAML::Node &input_files = yaml_config["input_files"];
79  for (size_t i = 0; i < input_files.size() && i < config.windows.size(); i++) {
80  config.windows[i].input_file = Get<std::string>(input_files[i], __FILE__, __LINE__);
81  }
82  }
83  } else {
84  // set up placeholders to be filled later
85  for (int i = 0; i < config.dynamic_n_windows; i++) {
86  WindowConfig window;
87  window.name = "Window_" + std::to_string(i);
88  window.center = 0.0; // Placeholder, will be updated from MaCh3_Config macro
89  window.width = 1.0; // Placeholder, will be updated from MaCh3_Config macro
90  config.windows.push_back(window);
91  }
92  }
93  } catch (const YAML::Exception &e) {
94  MACH3LOG_ERROR("Error parsing YAML file {}: {}", filename, e.what());
95  throw MaCh3Exception(__FILE__, __LINE__);
96  }
97 
98  return config;
99 }
100 
101 // Gaussian window function
102 double gaussianWindow(double x, double center, double width) {
103  return exp(-0.5 * pow((x - center) / width, 2)) / (width * sqrt(2 * TMath::Pi()));
104 }
105 
106 // von Mises window function (circular analog of Gaussian)
107 double vonMisesWindow(double x, double center, double kappa) {
108  // von Mises PDF: exp(kappa * cos(x - center)) / (2*pi*I0(kappa))
109  // For numerical stability, compute in log space when possible
110  if (kappa > 700) {
111  // Use asymptotic approximation for large kappa to avoid overflow
112  // log(I0(kappa)) ≈ kappa - 0.5*log(2*pi*kappa)
113  double log_I0 = kappa - 0.5 * log(2 * TMath::Pi() * kappa);
114  return exp(kappa * cos(x - center) - log_I0 - log(2 * TMath::Pi()));
115  } else {
116  double I0_kappa = TMath::BesselI0(kappa);
117  return exp(kappa * cos(x - center)) / (2 * TMath::Pi() * I0_kappa);
118  }
119 }
120 
121 // Generalised Gaussian window allows harsher constraints on the tails of the distribution, better for controlling windows in highly disfavoured regions
122 double generalisedGaussian2(double x, double mean, double width) {
123  constexpr int n = 2; // this controls the tightness of the gaussian fixed at 2
124  // for now due to normalisation
125  const double normFactor = 1 / ((M3::UmbrellaGaussianNormFactor) * 2 * std::sqrt(2) * width); // the normalisation is a little ugly (uses gamma functions),
126  // im just going to hardcode them for now
127  double likelihood = normFactor * std::exp(-std::pow((std::pow(x - mean, 2) / (2 * std::pow(width, 2))), n));
128  return likelihood;
129 }
130 
132 double GetMulticanonicalWeightGenGaussian(double deltacp, double mean, double width, double multicanonicalBeta) {
133  // implementation of the generalised gaussian as a bias function
134  // for now with a fixed n = 2 for simplicity
135 
136  double g0 = generalisedGaussian2(deltacp, mean, width);
137  double g1 = generalisedGaussian2(deltacp, mean - 2 * TMath::Pi(),width); // these two repeats are required for wrapping the gaussian around -+pi
138  double g2 = generalisedGaussian2(deltacp, mean + 2 * TMath::Pi(), width);
139  return (g0 + g1 + g2) * (multicanonicalBeta);
140 }
141 
142 // A sub calculation for the overlap matrix
143 // Sum of all windows weighted by z values
144 double summedWindowsWeighted(double x, const std::vector<WindowConfig> &windows, const std::vector<double> &z_values) {
145  double sum = 0.0;
146  for (size_t k = 0; k < windows.size(); k++) {
147  double window_val;
148  if (windows[k].umbrellaBiasFunction == M3::BiasFunction::kVonMises) {
149  window_val = vonMisesWindow(x, windows[k].center, windows[k].vonMises_kappa);
150  } else if (windows[k].umbrellaBiasFunction == M3::BiasFunction::kGaussian) {
151  window_val = gaussianWindow(x, windows[k].center, windows[k].width);
152  } else if (windows[k].umbrellaBiasFunction == M3::BiasFunction::kGeneralisedGaussian) {
153  window_val = GetMulticanonicalWeightGenGaussian(x, windows[k].center, windows[k].width, windows[k].multicanonical_beta);
154  } else {
155  MACH3LOG_ERROR("Unrecognised BiasFunction!!");
156  throw MaCh3Exception(__FILE__, __LINE__);
157  }
158  sum += window_val / z_values[k];
159  }
160  return sum;
161 }
162 
163 size_t calculateWindowCacheSizeBytes(const std::vector<WindowConfig> &windows, const std::vector<std::vector<double>> &samples) {
164  const size_t n_windows = windows.size();
165  size_t total_samples = 0;
166  for (const auto &sample_set : samples) {
167  total_samples += sample_set.size();
168  }
169  return n_windows * total_samples * sizeof(double);
170 }
171 
172 // Precompute all window evaluations once: cache[i][j][s] = window_j evaluated
173 // at samples[i][s]
174 // Memory heavy depending on number of steps/cores/windows
175 std::vector<std::vector<std::vector<double>>> buildWindowCache(const std::vector<WindowConfig> &windows,
176  const std::vector<std::vector<double>> &samples) {
177  TStopwatch clock;
178  clock.Start();
179  int n_windows = static_cast<int>(windows.size());
180  const size_t cache_size_bytes = calculateWindowCacheSizeBytes(windows, samples);
181  const double cache_size_gb = static_cast<double>(cache_size_bytes) / (1024.0 * 1024.0 * 1024.0);
182  if (cache_size_gb > 10.0) {
183  MACH3LOG_WARN("Window cache size: {:.2f} GB", cache_size_gb);
184  } else {
185  MACH3LOG_INFO("Window cache size: {:.2f} GB", cache_size_gb);
186  }
187 
188  std::vector<std::vector<std::vector<double>>> cache(n_windows);
189  for (int i = 0; i < n_windows; i++) {
190  cache[i].resize(n_windows);
191  for (int j = 0; j < n_windows; j++) {
192  cache[i][j].resize(samples[i].size());
193  }
194  }
195  MACH3LOG_INFO("Window cache built successfully.");
196 
197  #ifdef MULTITHREAD
198  #pragma omp parallel for collapse(2) schedule(dynamic)
199  #endif
200  for (int i = 0; i < n_windows; i++) {
201  for (int j = 0; j < n_windows; j++) {
202  for (size_t s = 0; s < samples[i].size(); s++) {
203  if (windows[j].umbrellaBiasFunction == M3::BiasFunction::kVonMises) {
204  cache[i][j][s] = vonMisesWindow(samples[i][s], windows[j].center, windows[j].vonMises_kappa);
205  } else if (windows[j].umbrellaBiasFunction == M3::BiasFunction::kGaussian) {
206  cache[i][j][s] = gaussianWindow(samples[i][s], windows[j].center, windows[j].width);
207  } else if (windows[j].umbrellaBiasFunction == M3::BiasFunction::kGeneralisedGaussian) {
208  cache[i][j][s] = GetMulticanonicalWeightGenGaussian(samples[i][s], windows[j].center, windows[j].width, windows[j].multicanonical_beta);
209  } else {
210  MACH3LOG_ERROR("Unrecognised BiasFunction!!");
211  throw MaCh3Exception(__FILE__, __LINE__);
212  }
213  }
214  }
215  }
216 
217  if (debug_mode) { // this should be TH1D of cache against sample value in the
218  // variable of interest for each window, to check the cache
219  // is being built correctly and the windows look correct
220  // across the range of samples
221  // TODO break this into a standalone function
222  // first print the size in memory of the cache to check it is reasonable and
223  // not being built incorrectly with an extra dimension or something
224  TFile *cache_file = TFile::Open("window_cache_debug_histograms.root", "RECREATE");
225  for (int i = 0; i < n_windows; i++) {
226  for (int j = 0; j < n_windows; j++) {
227  std::string hist_name = "Sample" + std::to_string(i) + "_window" + std::to_string(j);
228  TH1D *window_cache_hist = new TH1D(hist_name.c_str(), hist_name.c_str(), 100, -3.1415, 3.1415);
229  for (size_t s = 0; s < cache[i][j].size(); s++) {
230  window_cache_hist->AddBinContent(window_cache_hist->FindBin(samples[i][s]));
231  }
232  window_cache_hist->Write();
233  }
234  }
235 
236  // additionally save a TH2D of the values in the cache against the sample
237  // values for a specific window to check the shape of the windows is correct
238  // across the range of samples
239  for (int i = 0; i < n_windows; i++) {
240  for (int j = 0; j < n_windows; j++) {
241  std::string hist_name = "Sample" + std::to_string(i) + "_window" + std::to_string(j) + "_cache_2D";
242  TH2D *window_cache_2D_hist = new TH2D(hist_name.c_str(), hist_name.c_str(), 100, -3.1415, 3.1415, 100, -1000, 10);
243  for (size_t s = 0; s < cache[i][j].size(); s++) {
244  // take the log of the likelihood values
245  window_cache_2D_hist->Fill(samples[i][s], log(cache[i][j][s]));
246  }
247  // show overflow numbers on the hist
248  window_cache_2D_hist->SetStats(1);
249  window_cache_2D_hist->Write();
250  }
251  }
252 
253  cache_file->Close();
254  }
255 
256  clock.Stop();
257  MACH3LOG_INFO("Finished building window cache, it took {:.2f}s to finish", clock.RealTime());
258  return cache;
259 }
260 
261 // Main function to calculate the F matrix from a given set of samples plus weights
262 std::vector<std::vector<double>> calcFmatrix(const std::vector<double> &z_current,
263  const std::vector<WindowConfig> &windows,
264  const std::vector<std::vector<double>> &samples,
265  const std::vector<std::vector<std::vector<double>>> &window_cache) {
266  int n_windows = static_cast<int>(windows.size());
267  std::vector<std::vector<double>> F(n_windows, std::vector<double>(n_windows, 0.0));
268 
269  std::vector<double> z_inv = z_current; // make a copy to avoid modifying the original z_current
270  for (size_t i = 0; i < z_current.size(); i++) {
271  if (z_current[i] > 0) {
272  z_inv[i] = 1.0 / z_current[i];
273  } else {
274  z_inv[i] = 0.0; // Handle zero or negative z values gracefully
275  if (debug_mode) {
276  MACH3LOG_WARN("Warning: z_current[{}] is non-positive ({}). Setting its inverse to 0 in F matrix calculation.", i, z_current[i]);
277  }
278  }
279  }
280  #ifdef MULTITHREAD
281  #pragma omp parallel for schedule(dynamic)
282  #endif
283  for (int i = 0; i < n_windows; i++) {
284  std::vector<double> denominator_cache(samples[i].size(), 0.0);
285  for (size_t s = 0; s < samples[i].size(); s++) {
286  double denominator = 0.0;
287  for (int k = 0; k < n_windows; k++) {
288  denominator += window_cache[i][k][s] * z_inv[k];
289  }
290  denominator_cache[s] = 1 / denominator;
291  }
292 
293  for (int j = 0; j < n_windows; j++) {
294  double sum = 0.0;
295  int count = 0;
296 
297  for (size_t s = 0; s < samples[i].size(); s++) {
298  double sample = samples[i][s];
299  double window_j = window_cache[i][j][s];
300  double denominator = denominator_cache[s];
301 
302  if (denominator > 0) {
303  double integrand = (window_j * z_inv[i]) * denominator;
304  sum += integrand;
305  count++;
306  } else if (debug_mode) {
307  MACH3LOG_WARN("Denominator is zero for sample {} in window {}, skipping...", sample, i);
308  }
309  }
310 
311  if (debug_mode) {
312  MACH3LOG_INFO("F[{}][{}] sum: {}, count: {}", i, j, sum, count);
313  }
314 
315  if (count > 0) {
316  F[i][j] = sum / count;
317  }
318  }
319  }
320 
321  return F;
322 }
323 
324 // Z-solver function implementing the fixed point matrix iteration algorithm
325 std::vector<double> zSolver(const std::vector<double> &z_current,
326  const std::vector<WindowConfig> &windows,
327  const std::vector<std::vector<double>> &samples,
328  const std::vector<std::vector<std::vector<double>>> &window_cache,
329  [[maybe_unused]] bool verbose = false,
330  [[maybe_unused]] int *total_lines = nullptr) {
331  int n_windows = static_cast<int>(windows.size());
332 
333  // F matrix and update z values
334  std::vector<std::vector<double>> F = calcFmatrix(z_current, windows, samples, window_cache);
335  // Compute z_new = z_current * F
336  // could do faster matrix multiplication here?
337  std::vector<double> z_new(n_windows, 0.0);
338  for (int i = 0; i < n_windows; i++) {
339  for (int j = 0; j < n_windows; j++) {
340  z_new[i] += z_current[j] * F[j][i];
341  }
342  }
343 
356  //}
357 
358  // normalise the z values so that magnitude of the vector is 1
359  double z_magnitude = 0.0;
360  for (int i = 0; i < n_windows; i++) {
361  z_magnitude += z_new[i] * z_new[i];
362  }
363  z_magnitude = sqrt(z_magnitude);
364  if (z_magnitude > 0) {
365  for (int i = 0; i < n_windows; i++) {
366  z_new[i] /= z_magnitude;
367  }
368  }
369 
370  return z_new;
371 }
372 
374 // Check convergence
375 bool checkConvergence(const std::vector<double> &z_current, const std::vector<double> &z_prev, double tolerance) {
376  double sum_diffs = 0.0;
377  for (size_t i = 0; i < z_current.size(); i++) {
378  // if (std::abs(z_current[i] - z_prev[i]) > tolerance *
379  // std::max(std::abs(z_current[i]), std::abs(z_prev[i]))) { // intention
380  // here: is the difference between any of the z elements more than the
381  // tolerance*the magnitude of the largest element in z? in this way the
382  // tolerance sets the number of decimal points below the largest element we
383  // are targeting.
384  // return false;
385  // }
386  sum_diffs += std::abs(z_current[i] - z_prev[i]);
387  // check the average difference across all elements instead
388  // of requiring every element to meet the condition, this
389  // should be more robust to individual elements fluctuating
390  // around their target value while the overall z
391  // distribution is still converging
392  if (sum_diffs / static_cast<double>(z_current.size()) > tolerance) {
393  return false;
394  }
395  }
396  return true;
397 }
398 
399 std::vector<double> getZDiffs(const std::vector<double> &z_current, const std::vector<double> &z_prev) {
400  std::vector<double> diffs(z_current.size(), 0.0);
401  for (size_t i = 0; i < z_current.size(); i++) {
402  diffs[i] = std::abs(z_current[i] - z_prev[i]);
403  }
404  return diffs;
405 }
406 
407 // This implements a check on stalling of the evolution of the window weights. Seems to be a better definition for convergence that above
408 // moving-average stalled-convergence check on z values
409 bool checkConvergenceStalled(const std::vector<double> &z_current, const std::vector<double> &z_prev, double tolerance) {
410  (void)z_prev;
411 
412  static std::deque<std::vector<double>> z_history;
413  static std::vector<double> previous_moving_average;
414  static int stagnant_iterations = 0;
415 
416  constexpr int moving_average_window = 500;
417  constexpr int stagnant_required = 500;
418  const double bound = tolerance;
419 
420  if (z_current.empty()) {
421  return false;
422  }
423 
424  // Reset state safely if number of windows changes between solver runs.
425  if (!z_history.empty() && z_history.front().size() != z_current.size()) {
426  z_history.clear();
427  previous_moving_average.clear();
428  stagnant_iterations = 0;
429  }
430 
431  z_history.push_back(z_current);
432  if (static_cast<int>(z_history.size()) > moving_average_window) {
433  z_history.pop_front();
434  }
435 
436  // Wait until the moving-average window is fully populated.
437  if (static_cast<int>(z_history.size()) < moving_average_window) {
438  return false;
439  }
440 
441  std::vector<double> moving_average(z_current.size(), 0.0);
442  for (const auto &z_vec : z_history) {
443  for (size_t i = 0; i < z_vec.size(); i++) {
444  moving_average[i] += z_vec[i];
445  }
446  }
447  for (size_t i = 0; i < moving_average.size(); i++) {
448  moving_average[i] /= moving_average_window;
449  }
450 
451  if (previous_moving_average.empty()) {
452  previous_moving_average = moving_average;
453  return false;
454  }
455 
456  bool all_within_bound = true;
457  for (size_t i = 0; i < moving_average.size(); i++) {
458  if (std::abs(moving_average[i] - previous_moving_average[i]) > bound) {
459  all_within_bound = false;
460  break;
461  }
462  }
463 
464  if (all_within_bound) {
465  stagnant_iterations++;
466  } else {
467  stagnant_iterations = 0;
468  }
469 
470  previous_moving_average = moving_average;
471 
472  if (stagnant_iterations == stagnant_required) {
473  MACH3LOG_WARN("Convergence appears stalled: moving-average change stayed within {} for {} iterations.",
474  bound, stagnant_required);
475  }
476 
477  return stagnant_iterations >= stagnant_required;
478 }
479 
480 void LoadFiles(std::vector<std::vector<double>>& samples, UmbrellaConfig& config, std::vector<TFile *>& input_files, std::vector<TTree *>& input_trees) {
481  TStopwatch clock;
482  clock.Start();
483  if (!config.dynamic_files) {
484  MACH3LOG_INFO("Using static input files from configuration.");
485  for (size_t i = 0; i < config.windows.size(); i++) {
486  MACH3LOG_INFO("Loading file: {}", config.windows[i].input_file);
487  TFile *file = M3::Open(config.windows[i].input_file.c_str(), "READ", __FILE__, __LINE__);
488 
489  TTree *tree = static_cast<TTree*>(file->Get("posteriors"));
490  if (!tree) {
491  MACH3LOG_ERROR("Cannot find 'posteriors' tree in {}", config.windows[i].input_file);
492  file->Close();
493  continue;
494  }
495 
496  input_files.push_back(file);
497  input_trees.push_back(tree);
498  }
499  } else {
500  MACH3LOG_INFO("Dynamic file loading enabled. Searching for files in directory: {}", config.dynamic_pattern);
501  // Use ROOT's TSystem to find all files in the directory
502  TSystemDirectory dir("", config.dynamic_pattern.c_str());
503  TList *files = dir.GetListOfFiles();
504  int file_count = 0;
505  if (files) {
506  TIter next(files);
507  TSystemFile *file;
508  while ((file = static_cast<TSystemFile*>(next()))) {
509  std::string filename = file->GetName();
510  if (filename.find(".root") == std::string::npos) {
511  MACH3LOG_DEBUG("Skipping non-root file: {}", filename);
512  continue;
513  }
514 
515  std::string full_path = config.dynamic_pattern + "/" + filename;
516  MACH3LOG_DEBUG("Found file: {}", full_path);
517 
518  TFile *root_file = M3::Open(full_path, "READ", __FILE__, __LINE__);
519 
520  TTree *tree = static_cast<TTree*>(root_file->Get("posteriors"));
521  if (!tree) {
522  MACH3LOG_ERROR("Cannot find 'posteriors' tree in {}", full_path);
523  root_file->Close();
524  throw MaCh3Exception(__FILE__, __LINE__, "Missing 'posteriors' tree in file: " + full_path);
525  }
526  file_count++;
527 
528  input_files.push_back(root_file);
529  input_trees.push_back(tree);
530  MACH3LOG_DEBUG("Loaded tree 'posteriors' from file: {}", full_path);
531  }
532  } else {
533  MACH3LOG_ERROR("No files found matching pattern: {}", config.dynamic_pattern);
534  throw MaCh3Exception(__FILE__, __LINE__, "No files found matching pattern: " + config.dynamic_pattern);
535  }
536 
537  if (file_count != config.dynamic_n_windows) {
538  MACH3LOG_ERROR("Number of files found ({}) does not match expected dynamic_n_windows ({}).",
539  file_count, config.dynamic_n_windows);
540  throw MaCh3Exception(__FILE__, __LINE__, "File count mismatch for dynamic loading.");
541  }
542  }
543 
544  for (size_t i = 0; i < input_trees.size(); i++) {
545  TTree *tree = input_trees[i];
546  TFile *file = input_files[i];
547 
548  MACH3LOG_DEBUG("Processing file {}/{}: {}", i + 1, input_trees.size(), file->GetName());
549  TMacro *macro = static_cast<TMacro*>(file->Get("MaCh3_Config"));
550  if (macro) {
551  MACH3LOG_DEBUG("Found MaCh3_Config macro in file.");
552 
553  // Convert TMacro to YAML: concatenate all lines and parse
554  std::stringstream yaml_text;
555  TList *lines = macro->GetListOfLines();
556  for (int iline = 0; iline < lines->GetEntries(); ++iline) {
557  TObjString *line = static_cast<TObjString*>(lines->At(iline));
558  yaml_text << line->GetString().Data() << "\n";
559  }
560 
561  try {
562  YAML::Node macro_yaml = YAML::Load(yaml_text.str());
563  YAML::Node umbrellaConfig =
564  macro_yaml["General"]["MCMC"]["Multicanonical"];
565  // Extract window parameters
566  config.windows[i].center = Get<double>(umbrellaConfig["Umbrella"]["UmbrellaMean"], __FILE__, __LINE__);
567  MACH3LOG_DEBUG("Window {} center updated to {}", i, config.windows[i].center);
568 
569  config.variable_of_interest = Get<std::string>(umbrellaConfig["VariableName"], __FILE__, __LINE__);
570  config.windows[i].multicanonical_beta = Get<double>(umbrellaConfig["Beta"], __FILE__, __LINE__);
571 
572  // Check if using von Mises distribution
573  auto biasString = Get<std::string>(umbrellaConfig["Umbrella"]["UmbrellaBiasFunction"], __FILE__, __LINE__);
574  M3::BiasFunction biasMode = M3::ParseBiasFunction(biasString);
575  config.windows[i].umbrellaBiasFunction = biasMode;
576 
577  if (config.windows[i].umbrellaBiasFunction == M3::BiasFunction::kVonMises) {
578  // Extract von Mises sigma and compute kappa
579  double vonMises_sigma = Get<double>(umbrellaConfig["Umbrella"]["UmbrellaWidth"], __FILE__, __LINE__);
580  config.windows[i].vonMises_kappa = 1.0 / (vonMises_sigma * vonMises_sigma);
581  config.windows[i].width = vonMises_sigma; // Store sigma in width for reference
582  MACH3LOG_DEBUG("Window {} using von Mises: sigma = {}, kappa = {}",
583  i, vonMises_sigma, config.windows[i].vonMises_kappa);
584  } else {
585  // Extract Gaussian sigma
586  config.windows[i].width = Get<double>(umbrellaConfig["Umbrella"]["UmbrellaWidth"], __FILE__, __LINE__);
587  config.windows[i].vonMises_kappa = -1.0; // Not using von Mises
588  MACH3LOG_DEBUG("Window {} using Gaussian: width = {}", i, config.windows[i].width);
589  }
590  } catch (const std::exception &e) {
591  MACH3LOG_WARN("Could not parse macro as YAML: {}", e.what());
592  }
593  } else{
594  MACH3LOG_ERROR("Found MaCh3 chain without embedded config, this can't be right");
595  throw MaCh3Exception(__FILE__, __LINE__);
596  }
597 
598  double var_value;
599 
600  tree->SetBranchStatus("*", false);
601  const auto* branch = tree->GetBranch(config.variable_of_interest.c_str());
602  if (branch == nullptr) {
603  MACH3LOG_ERROR("Branch '{}' does not exist in the TTree.", config.variable_of_interest);
604  throw MaCh3Exception(__FILE__, __LINE__);
605  }
606  tree->SetBranchStatus(config.variable_of_interest.c_str(), true);
607  tree->SetBranchAddress(config.variable_of_interest.c_str(), &var_value);
608  Long64_t nentries = tree->GetEntries();
609  MACH3LOG_DEBUG("Window {}: {} entries", i, nentries);
610  samples[i].resize(nentries);
611  for (Long64_t entry = 0; entry < nentries; entry++) {
612  tree->GetEntry(entry);
613  samples[i][entry] = var_value;
614  }
615  // need to set all to true so later part of the code don't produce bonkers results
616  tree->SetBranchStatus("*", true);
617  }
618 
619  // Sort by window center and keep all per-window containers aligned.
620  // The final weighting stage loops over input_trees by index, so those indices
621  // must track the same sorted window order used by z_current.
622  for (size_t i = 0; i < config.windows.size(); i++) {
623  for (size_t j = i + 1; j < config.windows.size(); j++) {
624  if (config.windows[i].center > config.windows[j].center) {
625  std::swap(config.windows[i], config.windows[j]);
626  std::swap(samples[i], samples[j]);
627  std::swap(input_trees[i], input_trees[j]);
628  std::swap(input_files[i], input_files[j]);
629  }
630  }
631  }
632  clock.Stop();
633  MACH3LOG_INFO("Finished loading samples, it took {:.2f}s to finish", clock.RealTime());
634 
635  // verify the order and file associations after sorting
636  MACH3LOG_INFO("Final window configurations after sorting:");
637 
639  {"#", 5, 5, M3::Utils::Alignment::Right},
640  {"Filename", 10, 50, M3::Utils::Alignment::Left},
641  {"Center", 10, 15, M3::Utils::Alignment::Right},
642  {"Width", 10, 15, M3::Utils::Alignment::Right},
643  {"BiasFunction", 15, 30, M3::Utils::Alignment::Left},
644  {"Samples", 10, 20, M3::Utils::Alignment::Right},
645  });
646 
647  for (size_t i = 0; i < config.windows.size(); ++i) {
648  const auto& window = config.windows[i];
649  TFile* file = input_files[i];
650 
651  table.AddRow(i, file->GetName(),
652  M3::Utils::Cell(window.center, "{:.2f}"),
653  M3::Utils::Cell(window.width, "{:.2f}"),
654  M3::BiasFunction_ToString(window.umbrellaBiasFunction),
655  samples[i].size());
656  }
657 
658  for (const auto& line : table.Render()) {
659  MACH3LOG_INFO("{}", line);
660  }
661 }
662 
663 void DiagnoseUmbrella(const UmbrellaConfig& config, TFile * output_file, const std::vector<std::vector<double>>& z_evolution,
664  TTree *combined_tree) {
665  TDirectory* UmbrellaDir = output_file->mkdir("Umbrella");
666  UmbrellaDir->cd();
667 
668  TCanvas c1("c1", "Z Evolution", 800, 600);
669  std::vector<std::unique_ptr<TGraph>> z_graphs(config.windows.size());
670  TLegend legend(0.7, 0.7, 0.9, 0.9);
671 
672  double ymax = 0.0;
673  double ymin = std::numeric_limits<double>::max();
674 
675  for (size_t i = 0; i < config.windows.size(); i++) {
676  std::vector<double> iterations, z_vals;
677  for (size_t j = 0; j < z_evolution.size(); j++) {
678  iterations.push_back(static_cast<int>(j));
679  z_vals.push_back(z_evolution[j][i]);
680  }
681 
682  for (double val : z_vals) {
683  if (val > ymax)
684  ymax = val;
685  if (val < ymin)
686  ymin = val;
687  }
688 
689  z_graphs[i] = std::make_unique<TGraph>((iterations.size()), &iterations[0], &z_vals[0]);
690  z_graphs[i]->SetLineColor(static_cast<Color_t>(i + 1));
691  z_graphs[i]->SetLineWidth(2);
692  z_graphs[i]->SetName(Form("z_evolution_window_%lu", i));
693  z_graphs[i]->SetTitle("Evolution of Z Values");
694  z_graphs[i]->GetXaxis()->SetTitle("Iteration");
695  z_graphs[i]->GetYaxis()->SetTitle("Z Value");
696 
697  if (i == 0) {
698  z_graphs[i]->Draw("AL");
699  } else {
700  z_graphs[i]->Draw("L SAME");
701  }
702 
703  legend.AddEntry(z_graphs[i].get(), Form("Window %lu", i), "l");
704  z_graphs[i]->Write();
705  }
706 
707  legend.Draw();
708  z_graphs[0]->SetMaximum(ymax);
709  z_graphs[0]->SetMinimum(ymin);
710  c1.Update();
711  c1.SetLogy();
712  c1.Write();
713 
714  // Create summary histogram of umbrella_variable distribution
715  TH1D *h_delta_cp = new TH1D("h_delta_cp_weighted", "Weighted Delta CP Distribution", 100, -TMath::Pi(), TMath::Pi());
716  TH1D *h_delta_cp_unweighted = new TH1D("h_delta_cp_unweighted", "Unweighted Delta CP Distribution", 100, -TMath::Pi(), TMath::Pi());
717 
718  const std::string& variable = config.variable_of_interest;
719  combined_tree->Draw((variable + ">>h_delta_cp_weighted").c_str(), "umbrella_weight", "goff");
720  combined_tree->Draw((variable + ">>h_delta_cp_unweighted").c_str(), "", "goff");
721 
722  h_delta_cp->Write();
723  h_delta_cp_unweighted->Write();
724 
725  UmbrellaDir->Close();
726  delete UmbrellaDir;
727 }
728 
729 // Main function to run the umbrella sampling solver
730 void UmbrellaSolver(const std::string &config_file) {
731  MACH3LOG_INFO("=== Umbrella Sampling Z-Factor Solver ===");
732 
733  #ifdef MULTITHREAD
734  MACH3LOG_INFO("Max threads available: {}", omp_get_max_threads());
735  #else
736  MACH3LOG_WARN("OpenMP not available");
737  #endif
738  MACH3LOG_INFO("Loading configuration from: {}", config_file);
739 
740  // Parse configuration
741  UmbrellaConfig config = parseYAMLConfig(config_file);
742 
743  if (config.windows.empty() && !config.dynamic_files) {
744  MACH3LOG_ERROR("No windows defined in configuration and dynamic file loading is disabled.");
745  throw MaCh3Exception(__FILE__, __LINE__);
746  }
747  MACH3LOG_INFO("Variable of interest: {}", config.variable_of_interest);
748  MACH3LOG_INFO("Output file: {}", config.output_file);
749 
750  // Load data from input files
751  // Declare here to ensure it exists in the scope of the entire function
752  std::vector<std::vector<double>> samples;
753 
754  if (!config.dynamic_files) {
755  samples.resize(config.windows.size());
756  } else {
757  samples.resize(config.dynamic_n_windows);
758  }
759  std::vector<TFile*> input_files;
760  std::vector<TTree*> input_trees;
761 
762  LoadFiles(samples, config, input_files, input_trees);
763 
764  // Initialize z values
765  std::vector<double> z_current(config.windows.size(), 1.0);
766  std::vector<double> z_prev(config.windows.size(), 1.0);
767  std::vector<std::vector<double>> z_evolution;
768 
769  MACH3LOG_INFO("Starting iterative z-solver...");
770  int max_threads = M3::GetNThreads();
771  MACH3LOG_INFO("Max threads reported: {}", max_threads);
772 
773  MACH3LOG_INFO("Precomputing window cache...");
774  // TODO: this is slow as hell and causes massive memory usage, what's a smarter
775  // way to do this? break out to a file? RDataFrame?
776  std::vector<std::vector<std::vector<double>>> window_cache = buildWindowCache(config.windows, samples);
777 
778  // TFile to hold the F matrix evolution for the first 15 iterations if needed
779  bool save_matrix = true; // Set to true to enable saving F matrix evolution
780  // Create F_file here to avoid reopening it multiple times in the loop
781  TFile *F_file = nullptr;
782  if (save_matrix) {
783  size_t pos = config.output_file.find(".root");
784  std::string base_name = (pos != std::string::npos) ? config.output_file.substr(0, pos) : config.output_file;
785  F_file = TFile::Open((base_name + "_matrix_evolution.root").c_str(), "RECREATE"); // use name from config with .root subtracted with _matrix_evolution suffix added
786  if (!F_file || F_file->IsZombie()) {
787  MACH3LOG_ERROR("Cannot create file {}", base_name + "_matrix_evolution.root");
788  save_matrix = false; // Disable saving if file cannot be created
789  }
790  // add an initial FMatrix with the initial z values for reference
791  std::vector<std::vector<double>> initial_F = calcFmatrix(z_current, config.windows, samples, window_cache);
792  int n_windows = static_cast<int>(config.windows.size());
793  TH2D initial_F_TH2D("F_matrix_initial", "Initial F matrix;Window j;Window i", n_windows, 0, n_windows, n_windows, 0, n_windows);
794  for (int i = 0; i < n_windows; i++) {
795  for (int j = 0; j < n_windows; j++) {initial_F_TH2D.SetBinContent(j + 1, i + 1, initial_F[i][j]); // Note the order of i and j for correct axis labeling
796  }
797  }
798  F_file->cd();
799  MACH3LOG_INFO("Saving initial F matrix to file...");
800  initial_F_TH2D.Write();
801  }
802 
803  // Timing variables
804  auto start_time = std::chrono::high_resolution_clock::now();
805  auto last_print_time = start_time;
806 
807  bool converged_robustness_check = false; // Flag to indicate if convergence check has been passed at least
808  // once, used to control when to start checking for stalled
809  // convergence
810 
811  if (!converged_robustness_check) {
812  MACH3LOG_INFO("Starting iterative solver with convergence checks...");
813  }
814  // Iterative solver
815  int total_output_lines = 0; // Track total lines printed for clearing
816  for (int iteration = 0; iteration < config.max_iterations; iteration++) {
817  if (iteration % config.print_frequency == 0 || iteration == 1) {
818  auto current_time = std::chrono::high_resolution_clock::now();
819  // Calculate average relative change (precision metric)
820  double avg_relative_change = 0.0;
821  if (iteration > 0) {
822  for (size_t i = 0; i < z_current.size(); i++) {
823  double rel_change = std::abs(z_current[i] - z_prev[i]) / std::max(std::abs(z_current[i]), 1e-10);
824  avg_relative_change += rel_change;
825  }
826  avg_relative_change /= static_cast<double>(z_current.size());
827  }
828 
829  // Print z-values
830  std::string z_values = "[";
831  for (size_t i = 0; i < z_current.size(); i++) {
832  z_values += fmt::format("{:10.5f}", z_current[i]);
833  if (i < z_current.size() - 1) {
834  z_values += ", ";
835  }
836  }
837  z_values += "]";
838  if (iteration > 0) {
839  auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(current_time - last_print_time);
840  double avg_time_per_iteration = static_cast<double>(duration.count()) /
841  static_cast<double>(config.print_frequency);
842  MACH3LOG_INFO("Iteration {:6}: z values: {} (avg: {:6.1f} ms/iter)", iteration, z_values, avg_time_per_iteration);
843  MACH3LOG_INFO("Avg relative change: {:.3e} (target: {:.3e})", avg_relative_change, config.tolerance);
844  total_output_lines = 2;
845  } else {
846  MACH3LOG_INFO("Iteration {:6}: z values: {}", iteration, z_values);
847  total_output_lines = 1;
848  }
849  last_print_time = current_time;
850  }
851 
852  z_prev = z_current;
853  z_current = zSolver(z_current, config.windows, samples, window_cache, iteration % config.print_frequency == 0, &total_output_lines);
854  z_evolution.push_back(z_current);
855 
856  // save the first 15 iterations of the F matrix to check convergence
857  // behaviour and debug if needed should be a root file with Th2D for easy
858  // plotting in root, with axes of iteration number and window index, and the
859  // value being the F matrix element
860  if (save_matrix && (iteration < 15 || iteration % config.print_frequency == 0)) {
861  std::vector<std::vector<double>> F_matrix = calcFmatrix(z_current, config.windows, samples, window_cache);
862  // convert F_matrix to Th2D for saving to root file
863  int n_windows = static_cast<int>(config.windows.size());
864  TH2D F_TH2D(Form("F_matrix_iter_%02d", iteration),Form("F matrix at iteration %02d;Window j;Window i", iteration), n_windows, 0, n_windows, n_windows, 0, n_windows);
865  for (int i = 0; i < n_windows; i++) {
866  for (int j = 0; j < n_windows; j++) {
867  F_TH2D.SetBinContent(j + 1, i + 1, F_matrix[i][j]); // Note the order of i and j for correct axis labeling
868  }
869  }
870  // for the purposes of picking back up a solve after it has been
871  // interrupted also save the std::vector of z_current. You can put this into hacky start to pick up
872  TTree *z_tree = new TTree(Form("z_saved_iter_%02d", iteration), Form("Z vector at iteration %02d", iteration));
873  z_tree->Branch("z_saved", &z_current);
874  z_tree->Fill();
875  F_file->cd();
876  MACH3LOG_INFO("Saving F matrix for iteration {} to file...", iteration);
877  F_TH2D.Write();
878  z_tree->Write();
879  }
880  // if (save_matrix && iteration == 15) {
881  // F_file->Close();
882  // }
883 
884  // after the first convergence check has been passed, randomly perturb the z
885  // values to see if they return to the same values, this is a robustness
886  // check to see if the solution is stable or if it is just meeting the
887  // convergence criteria by chance due to small changes in z values
888  bool apply_robustness_check = true;
889  if (iteration % 100 == 0 &&
890  (checkConvergence(z_current, z_prev, config.tolerance) || checkConvergenceStalled(z_current, z_prev, config.tolerance))) {
891  if (!converged_robustness_check && apply_robustness_check) {
892  MACH3LOG_INFO("Convergence check passed at iteration {}. Starting robustness check with random perturbation...", iteration);
893  converged_robustness_check = true;
894 
895  // Apply random perturbation to z_current
896  std::vector<double> z_perturbed = z_current;
897  for (size_t i = 0; i < z_perturbed.size(); i++) {
898  // Random perturbation up to 10 times the tolerance
899  double perturbation = M3::rand::Uniform(-0.5, 0.5) * z_perturbed[i];
900  z_perturbed[i] += perturbation;
901  if (z_perturbed[i] < 0) {
902  z_perturbed[i] = abs(z_perturbed[i]); // Ensure no negative values
903  }
904  MACH3LOG_INFO("z[{:2}] | Original: {:.6e} | Perturbation: {:+.6e} | Updated: {:.6e}",
905  i, z_current[i], perturbation, z_perturbed[i]);
906  }
907  z_current = z_perturbed;
908  MACH3LOG_INFO("Applied random perturbation to z values for robustness check.");
909  } else {
910  if (checkConvergence(z_current, z_prev, config.tolerance)) {
911  MACH3LOG_INFO("Convergence achieved at iteration {}", iteration);
912  } else {
913  MACH3LOG_WARN("Convergence appears to be stalled at iteration {}", iteration);
914  }
915 
916  if (iteration == config.max_iterations - 1) {
917  MACH3LOG_WARN("Reached maximum iterations without convergence.");
918  }
919 
920  auto end_time = std::chrono::high_resolution_clock::now();
921  auto total_duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
922  double avg_time_total = static_cast<double>(total_duration.count()) /
923  static_cast<double>(iteration + 1);
924  if (save_matrix) {
925  F_file->Close();
926  }
927  MACH3LOG_INFO("Terminating at iteration {}", iteration);
928  MACH3LOG_INFO("Average time per iteration: {:.6f} ms", avg_time_total);
929  break;
930  }
931  }
932  }
933 
934  std::ostringstream oss;
935  oss << "Final z values: [";
936  for (size_t i = 0; i < z_current.size(); i++) {
937  oss << std::fixed << std::setprecision(5) << z_current[i];
938  if (i < z_current.size() - 1)
939  oss << ", ";
940  }
941  oss << "]";
942  MACH3LOG_INFO("{}", oss.str());
943 
944  std::filesystem::copy_file(input_files[0]->GetName(), config.output_file,
945  std::filesystem::copy_options::overwrite_existing);
946  // Create output file
947  TFile *output_file = M3::Open(config.output_file.c_str(), "UPDATE", __FILE__, __LINE__);
948  output_file->cd();
949 
950  TTree *input_tree = dynamic_cast<TTree*>(output_file->Get("posteriors"));
951  // Create combined tree with weights
952  TTree *combined_tree = input_tree->CloneTree(0);
953 
954  // Variables for the combined tree
955  double umbrella_weight;
956  int window_id;
957  double umbrella_variable;
958 
959  combined_tree->Branch("umbrella_weight", &umbrella_weight, "umbrella_weight/D");
960  combined_tree->Branch("window_id", &window_id, "window_id/I");
961 
962  // Fill combined tree
963  for (size_t i = 0; i < input_trees.size(); i++) {
964  TTree *tree = input_trees[i];
965 
966  Long64_t nentries = tree->GetEntries();
967 
968  // KS: This is to avoid warnings about missing umbrella branches...
969  int oldLevel = gErrorIgnoreLevel;
970  gErrorIgnoreLevel = kError;
971  combined_tree->CopyAddresses(tree);
972  gErrorIgnoreLevel = oldLevel;
973 
974  tree->SetBranchAddress(config.variable_of_interest.c_str(), &umbrella_variable);
975  // KS: SetBranchAddress above decouples the input branch address, so update the
976  // copied output branch address to use the current umbrella_variable value.
977  combined_tree->GetBranch(config.variable_of_interest.c_str())->SetAddress(&umbrella_variable);
978  if (z_current[i] == 0) {
979  MACH3LOG_WARN("Z value for window {} is zero, skipping weighting for this window to avoid division by zero.", i);
980  }
981  window_id = static_cast<int>(i);
982 
983  for (Long64_t entry = 0; entry < nentries; entry++) {
984  tree->GetEntry(entry);
985 
986  if (z_current[i] == 0) {
987  umbrella_weight = 0.0; // If z is zero, we cannot apply the umbrella weight, so we set it to 0 (completely downweigh this window's contribution)
988  } else {
989  // Calculate umbrella weight for this event
990  // The umbrella weight corrects for the bias introduced by the window function Weight is 1 / sum of all window contributions (equation 4 from paper)
991  double denominator = 1 / summedWindowsWeighted(umbrella_variable, config.windows, z_current);
992 
993  // umbrella_weight = z_current[i] / denominator; // with or without z_current[i] / denominator? why did I have this originally
994  umbrella_weight = denominator; // This is the correct form based on the paper - the z_current[i] factor is already included in the summedWindowsWeighted function
995  }
996 
997  if (combined_tree->Fill() < 0) {
998  MACH3LOG_WARN("Failed writing output tree. Check disk quota/space and write permissions for: {}", config.output_file);
999  throw MaCh3Exception(__FILE__, __LINE__);
1000  }
1001  }
1002  }
1003  // Write final results
1004  combined_tree->Write(input_tree->GetName(), TObject::kOverwrite);
1005 
1006  // Save diagnostics
1007  DiagnoseUmbrella(config, output_file, z_evolution, combined_tree);
1008 
1009  // Get entry count before closing the file
1010  Long64_t total_entries = combined_tree->GetEntries();
1011 
1012  output_file->cd();
1013 
1014  YAML::Node yaml_config = M3OpenConfig(config_file);
1015  YAML::Node umbrella_config;
1016  umbrella_config["UmbrellaSolver"] = yaml_config["UmbrellaSolver"];
1017 
1018  // Convert YAML -> TMacro
1019  TMacro UmbrellaHeader = YAMLtoTMacro(umbrella_config, "Umbrella_Config");
1020  UmbrellaHeader.Write();
1021 
1022  output_file->Close();
1023 
1024  // Close input files
1025  for (TFile *file : input_files) {
1026  file->Close();
1027  }
1028 
1029  MACH3LOG_INFO("Umbrella sampling outputs created (make sure to check for convergence issues)!");
1030  MACH3LOG_INFO("Output written to: {}", config.output_file);
1031  MACH3LOG_INFO("Combined tree contains {} entries with umbrella weights.", total_entries);
1032 }
1033 
1034 // Main function for compiled version
1035 int main(int argc, char *argv[]) {
1037  std::string config_file = "umbrella_config.yaml";
1038  if (argc > 1) {
1039  config_file = argv[1];
1040  }
1041 
1042  try {
1043  UmbrellaSolver(config_file);
1044  } catch (const std::exception &e) {
1045  MACH3LOG_ERROR("Error: {}", e.what());
1046  return 1;
1047  }
1048 
1049  return 0;
1050 }
#define _MaCh3_Safe_Include_Start_
KS: Avoiding warning checking for headers.
Definition: Core.h:126
#define _MaCh3_Safe_Include_End_
#define MACH3LOG_DEBUG
Definition: MaCh3Logger.h:34
#define MACH3LOG_ERROR
Definition: MaCh3Logger.h:37
#define MACH3LOG_INFO
Definition: MaCh3Logger.h:35
void SetMaCh3LoggerFormat()
Set messaging format of the logger.
Definition: MaCh3Logger.h:60
#define MACH3LOG_WARN
Definition: MaCh3Logger.h:36
std::vector< double > getZDiffs(const std::vector< double > &z_current, const std::vector< double > &z_prev)
int main(int argc, char *argv[])
void LoadFiles(std::vector< std::vector< double >> &samples, UmbrellaConfig &config, std::vector< TFile * > &input_files, std::vector< TTree * > &input_trees)
void DiagnoseUmbrella(const UmbrellaConfig &config, TFile *output_file, const std::vector< std::vector< double >> &z_evolution, TTree *combined_tree)
double summedWindowsWeighted(double x, const std::vector< WindowConfig > &windows, const std::vector< double > &z_values)
double vonMisesWindow(double x, double center, double kappa)
double GetMulticanonicalWeightGenGaussian(double deltacp, double mean, double width, double multicanonicalBeta)
double generalisedGaussian2(double x, double mean, double width)
void UmbrellaSolver(const std::string &config_file)
bool checkConvergence(const std::vector< double > &z_current, const std::vector< double > &z_prev, double tolerance)
A few different convergence checks.
UmbrellaConfig parseYAMLConfig(const std::string &filename)
YAML-based config parser using yaml-cpp library.
bool checkConvergenceStalled(const std::vector< double > &z_current, const std::vector< double > &z_prev, double tolerance)
double gaussianWindow(double x, double center, double width)
_MaCh3_Safe_Include_Start_ _MaCh3_Safe_Include_End_ bool debug_mode
std::vector< std::vector< std::vector< double > > > buildWindowCache(const std::vector< WindowConfig > &windows, const std::vector< std::vector< double >> &samples)
std::vector< std::vector< double > > calcFmatrix(const std::vector< double > &z_current, const std::vector< WindowConfig > &windows, const std::vector< std::vector< double >> &samples, const std::vector< std::vector< std::vector< double >>> &window_cache)
size_t calculateWindowCacheSizeBytes(const std::vector< WindowConfig > &windows, const std::vector< std::vector< double >> &samples)
std::vector< double > zSolver(const std::vector< double > &z_current, const std::vector< WindowConfig > &windows, const std::vector< std::vector< double >> &samples, const std::vector< std::vector< std::vector< double >>> &window_cache, [[maybe_unused]] bool verbose=false, [[maybe_unused]] int *total_lines=nullptr)
TMacro YAMLtoTMacro(const YAML::Node &yaml_node, const std::string &name)
Convert a YAML node to a ROOT TMacro object.
Definition: YamlHelper.h:166
#define M3OpenConfig(filename)
Macro to simplify calling LoadYaml with file and line info.
Definition: YamlHelper.h:589
Creates fancy formatted text tables for terminal output.
Definition: TablePrinter.h:50
Custom exception class used throughout MaCh3.
static std::string Cell(T &&value, std::string_view format)
Format a value using a runtime fmt format string.
Definition: TablePrinter.h:44
@ Right
Align text to the right.
@ Left
Align text to the left.
double Uniform(const double min=0.0, const double max=1.0)
Generate a uniform random number in [min, max).
Definition: Random.h:116
M3::BiasFunction ParseBiasFunction(const std::string &biasFunctionName)
std::string BiasFunction_ToString(const M3::BiasFunction &biasFunction)
TFile * Open(const std::string &Name, const std::string &Type, const std::string &File, const int Line)
Opens a ROOT file with the given name and mode.
int GetNThreads()
number of threads which we need for example for TRandom3
Definition: Monitor.cpp:372
constexpr double UmbrellaGaussianNormFactor
@ kGaussian
Assumes gaussian prior.
std::string variable_of_interest
std::vector< WindowConfig > windows
std::string output_file
std::string dynamic_pattern
double multicanonical_beta
std::string name
std::string input_file
double vonMises_kappa
M3::BiasFunction umbrellaBiasFunction