MaCh3  2.6.0
Reference Guide
UmbrellaSolver.cpp
Go to the documentation of this file.
1 
7 #include "Manager/Manager.h"
9 
11 #include "TSystem.h"
12 #include "TChain.h"
13 #include "TSystemDirectory.h"
15 
16 //this file has lots of usage of the ROOT plotting interface that only takes floats, turn this warning off for this CU for now
17 #pragma GCC diagnostic ignored "-Wfloat-conversion"
18 #pragma GCC diagnostic ignored "-Wold-style-cast"
19 #pragma GCC diagnostic ignored "-Wunused-parameter"
20 #pragma GCC diagnostic ignored "-Wunused-variable"
21 #pragma GCC diagnostic ignored "-Wunused-but-set-variable"
22 #pragma GCC diagnostic ignored "-Wconversion"
23 
24 bool debug_mode = false;
25 
26 // Structure to hold each window configuration
27 // These are individual window settings, function type & params plus filename
28 struct WindowConfig {
29  std::string name;
31  double center;
32  double width;
33  std::string input_file;
35 };
36 
37 // Structure to hold umbrella configuration
38 // This is the global settings of the umbrella fit eg number of windows and solve settings
40  std::vector<WindowConfig> windows;
41  std::string output_file;
42  std::string variable_of_interest;
44  std::string dynamic_pattern;
47  double tolerance;
49  bool use_openmp;
50 };
51 
52 // YAML-based config parser using yaml-cpp library
53 UmbrellaConfig parseYAMLConfig(const std::string &filename) {
55 
56  try {
57  YAML::Node yaml_diag_config = YAML::LoadFile(filename);
58  YAML::Node yaml_config = yaml_diag_config["UmbrellaSolver"];
59 
60  // Parse other configuration
61  if (yaml_config["output_file"]) {
62  config.output_file = yaml_config["output_file"].as<std::string>();
63  }
64  if (yaml_config["variable_of_interest"]) {
65  config.variable_of_interest =
66  yaml_config["variable_of_interest"].as<std::string>();
67  }
68  if (yaml_config["max_iterations"]) {
69  config.max_iterations = yaml_config["max_iterations"].as<int>();
70  }
71  if (yaml_config["tolerance"]) {
72  config.tolerance = yaml_config["tolerance"].as<double>();
73  }
74  if (yaml_config["print_frequency"]) {
75  config.print_frequency = yaml_config["print_frequency"].as<int>();
76  }
77  if (yaml_config["dynamic_files"]) {
78  config.dynamic_files = yaml_config["dynamic_files"].as<bool>();
79  } else {
80  config.dynamic_files = false; // Default to false
81  }
82  if (yaml_config["dynamic_pattern"]) {
83  config.dynamic_pattern = yaml_config["dynamic_pattern"].as<std::string>();
84  }
85  if (yaml_config["dynamic_n_windows"]) {
86  config.dynamic_n_windows = yaml_config["dynamic_n_windows"].as<int>();
87  }
88  if (yaml_config["use_openmp"]) {
89  config.use_openmp = yaml_config["use_openmp"].as<bool>();
90  } else {
91  config.use_openmp = true; // Default to enabled
92  }
93 
94  if (!config.dynamic_files) {
95  // TODO: these are mostly redundant now that the MaCh3_Config macro is
96  // being parsed, work out if its safe to remove them at somepoint!!! Parse
97  // windows
98  if (yaml_config["windows"]) {
99  const YAML::Node &windows = yaml_config["windows"];
100  for (size_t i = 0; i < windows.size(); i++) {
101  WindowConfig window;
102  window.name = windows[i]["name"].as<std::string>();
103  window.center = windows[i]["center"].as<double>();
104  window.width = windows[i]["width"].as<double>();
105  config.windows.push_back(window);
106  }
107  }
108 
109  // Parse input files
110  if (yaml_config["input_files"]) {
111  const YAML::Node &input_files = yaml_config["input_files"];
112  for (size_t i = 0; i < input_files.size() && i < config.windows.size();
113  i++) {
114  config.windows[i].input_file = input_files[i].as<std::string>();
115  }
116  }
117  } else {
118  // set up placeholders to be filled later
119  for (int i = 0; i < config.dynamic_n_windows; i++) {
120  WindowConfig window;
121  window.name = "Window_" + std::to_string(i);
122  window.center =
123  0.0; // Placeholder, will be updated from MaCh3_Config macro
124  window.width =
125  1.0; // Placeholder, will be updated from MaCh3_Config macro
126  config.windows.push_back(window);
127  }
128  }
129 
130  } catch (const YAML::Exception &e) {
131  std::cerr << "Error parsing YAML file " << filename << ": " << e.what()
132  << std::endl;
133  throw;
134  }
135 
136  return config;
137 }
138 
139 // Gaussian window function
140 double gaussianWindow(double x, double center, double width) {
141  return exp(-0.5 * pow((x - center) / width, 2)) / (width * sqrt(2 * TMath::Pi()));
142 }
143 
144 // von Mises window function (circular analog of Gaussian)
145 double vonMisesWindow(double x, double center, double kappa) {
146  // von Mises PDF: exp(kappa * cos(x - center)) / (2*pi*I0(kappa))
147  // For numerical stability, compute in log space when possible
148  double I0_kappa;
149  if (kappa > 700) {
150  // Use asymptotic approximation for large kappa to avoid overflow
151  // log(I0(kappa)) ≈ kappa - 0.5*log(2*pi*kappa)
152  double log_I0 = kappa - 0.5 * log(2 * TMath::Pi() * kappa);
153  return exp(kappa * cos(x - center) - log_I0 - log(2 * TMath::Pi()));
154  } else {
155  I0_kappa = TMath::BesselI0(kappa);
156  return exp(kappa * cos(x - center)) / (2 * TMath::Pi() * I0_kappa);
157  }
158 }
159 
160 // Generalised Gaussian window allows harsher constraints on the tails of the distribution, better for controlling windows in highly disfavoured regions
161 double generalisedGaussian2(double x, double mean, double width) {
162  constexpr int n = 2; // this controls the tightness of the gaussian fixed at 2
163  // for now due to normalisation
164  const double normFactor = 1 / ((0.906402477055) * 2 * std::sqrt(2) * width); // the normalisation is a little ugly (uses gamma functions),
165  // im just going to hardcode them for now
166  double likelihood = normFactor * std::exp(-std::pow((std::pow(x - mean, 2) / (2 * std::pow(width, 2))), n));
167  return likelihood;
168 }
169 
170 // TODO modify this to instead use atan2 implementation for wrapping
171 double GetMulticanonicalWeightGenGaussian(double deltacp, double mean, double width) {
172  // implemenetation of the generalised gaussian as a bias function
173  // for now with a fixed n = 2 for simplicity
174 
175  double g0 = generalisedGaussian2(deltacp, mean, width);
176  double g1 = generalisedGaussian2(deltacp, mean - 2 * TMath::Pi(),width); // these two repeats are required for wrapping the gaussian around -+pi
177  double g2 = generalisedGaussian2(deltacp, mean + 2 * TMath::Pi(), width);
178  double multicanonicalBeta = 1.0;
179  return (g0 + g1 + g2) * (multicanonicalBeta);
180 }
181 
182 // A sub calculation for the overlap matrix
183 // Sum of all windows weighted by z values
184 double summedWindowsWeighted(double x, const std::vector<WindowConfig> &windows, const std::vector<double> &z_values) {
185  double sum = 0.0;
186  for (size_t k = 0; k < windows.size(); k++) {
187  double window_val;
188  if (windows[k].umbrellaBiasFunction == M3::BiasFunction::kVonMises) {
189  window_val = vonMisesWindow(x, windows[k].center, windows[k].vonMises_kappa);
190  } else if (windows[k].umbrellaBiasFunction == M3::BiasFunction::kGaussian) {
191  window_val = gaussianWindow(x, windows[k].center, windows[k].width);
192  } else if (windows[k].umbrellaBiasFunction == M3::BiasFunction::kGeneralisedGaussian) {
193  window_val = GetMulticanonicalWeightGenGaussian(x, windows[k].center, windows[k].width);
194  } else {
195  std::cout << "Unrecognised BiasFunction!!" << std::endl;
196  }
197  sum += window_val / z_values[k];
198  }
199  return sum;
200 }
201 
202 // Precompute all window evaluations once: cache[i][j][s] = window_j evaluated
203 // at samples[i][s]
204 // Memory heavy depending on number of steps/cores/windows
205 std::vector<std::vector<std::vector<double>>> buildWindowCache(const std::vector<WindowConfig> &windows, const std::vector<std::vector<double>> &samples, bool use_openmp = true) {
206 
207  int n_windows = windows.size();
208  std::vector<std::vector<std::vector<double>>> cache(n_windows);
209 
210  for (int i = 0; i < n_windows; i++) {
211  cache[i].resize(n_windows);
212  for (int j = 0; j < n_windows; j++) {
213  cache[i][j].resize(samples[i].size());
214  }
215  }
216 
217  if (use_openmp) {
218  #ifdef MULTITHREAD
219  #pragma omp parallel for collapse(2) schedule(dynamic)
220  #endif
221  for (int i = 0; i < n_windows; i++) {
222  for (int j = 0; j < n_windows; j++) {
223  for (size_t s = 0; s < samples[i].size(); s++) {
224  if (windows[j].umbrellaBiasFunction == M3::BiasFunction::kVonMises) {
225  cache[i][j][s] = vonMisesWindow(samples[i][s], windows[j].center, windows[j].vonMises_kappa);
226  } else if (windows[j].umbrellaBiasFunction == M3::BiasFunction::kGaussian) {
227  cache[i][j][s] = gaussianWindow(samples[i][s], windows[j].center, windows[j].width);
228  } else if (windows[j].umbrellaBiasFunction == M3::BiasFunction::kGeneralisedGaussian) {
229  cache[i][j][s] = GetMulticanonicalWeightGenGaussian(samples[i][s], windows[j].center, windows[j].width);
230  } else {
231  std::cout << "Unrecognised function!!!!!" << std::endl;
232  }
233  }
234  }
235  }
236  } else {
237  for (int i = 0; i < n_windows; i++) {
238  for (int j = 0; j < n_windows; j++) {
239  for (size_t s = 0; s < samples[i].size(); s++) {
240  if (windows[j].umbrellaBiasFunction == M3::BiasFunction::kVonMises) {
241  cache[i][j][s] = vonMisesWindow(samples[i][s], windows[j].center, windows[j].vonMises_kappa);
242  } else if (windows[j].umbrellaBiasFunction == M3::BiasFunction::kGaussian) {
243  cache[i][j][s] = gaussianWindow(samples[i][s], windows[j].center, windows[j].width);
244  } else if (windows[j].umbrellaBiasFunction == M3::BiasFunction::kGeneralisedGaussian) {
245  cache[i][j][s] = GetMulticanonicalWeightGenGaussian(samples[i][s], windows[j].center, windows[j].width);
246  } else {
247  std::cout << "Unrecognised function!!!!!" << std::endl;
248  }
249  }
250  }
251  }
252  }
253 
254  if (debug_mode) { // this should be TH1D of cache against sample value in the
255  // variable of interest for each window, to check the cache
256  // is being built correctly and the windows look correct
257  // across the range of samples
258  // TODO break this into a standalone function
259  // first print the size in memory of the cache to check it is reasonable and
260  // not being built incorrectly with an extra dimension or something
261  size_t cache_size_bytes = 0;
262  for (int i = 0; i < n_windows; i++) {
263  for (int j = 0; j < n_windows; j++) {
264  cache_size_bytes += cache[i][j].size() * sizeof(double);
265  }
266  }
267  std::cout << "Window cache size: " << cache_size_bytes / (1024.0 * 1024.0) << " MB" << std::endl;
268 
269  std::cout << "Window cache built successfully." << std::endl;
270  TFile *cache_file = TFile::Open("window_cache_debug_histograms.root", "RECREATE");
271  for (int i = 0; i < n_windows; i++) {
272  for (int j = 0; j < n_windows; j++) {
273  std::string hist_name = "Sample" + std::to_string(i) + "_window" + std::to_string(j);
274  TH1D *window_cache_hist = new TH1D(hist_name.c_str(), hist_name.c_str(), 100, -3.1415, 3.1415);
275  for (size_t s = 0; s < cache[i][j].size(); s++) {
276  window_cache_hist->AddBinContent(window_cache_hist->FindBin(samples[i][s]));
277  }
278  window_cache_hist->Write();
279  }
280  }
281 
282  // additionally save a TH2D of the values in the cache against the sample
283  // values for a specific window to check the shape of the windows is correct
284  // across the range of samples
285  for (int i = 0; i < n_windows; i++) {
286  for (int j = 0; j < n_windows; j++) {
287  std::string hist_name = "Sample" + std::to_string(i) + "_window" + std::to_string(j) + "_cache_2D";
288  TH2D *window_cache_2D_hist = new TH2D(hist_name.c_str(), hist_name.c_str(), 100, -3.1415, 3.1415, 100, -1000, 10);
289  for (size_t s = 0; s < cache[i][j].size(); s++) {
290  // take the log of the likelihood values
291  window_cache_2D_hist->Fill(samples[i][s], log(cache[i][j][s]));
292  }
293  // show overflow numbers on the hist
294  window_cache_2D_hist->SetStats(1);
295  window_cache_2D_hist->Write();
296  }
297  }
298 
299  cache_file->Close();
300  }
301 
302  return cache;
303 }
304 
305 // Main function to calculate the F matrix from a given set of samples plus weights
306 std::vector<std::vector<double>> calcFmatrix(std::vector<double> &z_current,
307  const std::vector<WindowConfig> &windows,
308  const std::vector<std::vector<double>> &samples,
309  const std::vector<std::vector<std::vector<double>>> &window_cache,
310  bool use_openmp = true) {
311 
312  int n_windows = windows.size();
313  std::vector<std::vector<double>> F(n_windows, std::vector<double>(n_windows, 0.0));
314 
315  std::vector<double> z_inv = z_current; // make a copy to avoid modifying the original z_current
316  for (size_t i = 0; i < z_current.size(); i++) {
317  if (z_current[i] > 0) {
318  z_inv[i] = 1.0 / z_current[i];
319  } else {
320  z_inv[i] = 0.0; // Handle zero or negative z values gracefully
321  if (debug_mode) {
322  std::cerr << "Warning: z_current[" << i << "] is non-positive (" << z_current[i] << "). Setting its inverse to 0 in F matrix calculation." << std::endl;
323  }
324  }
325  }
326 
327  if (use_openmp) {
328  #ifdef MULTITHREAD
329  #pragma omp parallel for schedule(dynamic)
330  #endif
331  for (int i = 0; i < n_windows; i++) {
332  std::vector<double> denominator_cache(samples[i].size(), 0.0);
333  for (size_t s = 0; s < samples[i].size(); s++) {
334  double denominator = 0.0;
335  for (int k = 0; k < n_windows; k++) {
336  denominator += window_cache[i][k][s] * z_inv[k];
337  }
338  denominator_cache[s] = 1 / denominator;
339  }
340 
341  for (int j = 0; j < n_windows; j++) {
342  double sum = 0.0;
343  int count = 0;
344 
345  for (size_t s = 0; s < samples[i].size(); s++) {
346  double sample = samples[i][s];
347  double window_j = window_cache[i][j][s];
348  double denominator = denominator_cache[s];
349 
350  if (denominator > 0) {
351  double integrand = (window_j * z_inv[i]) * denominator;
352  sum += integrand;
353  count++;
354  } else if (debug_mode) {
355  printf("Denominator is zero for sample %f in window %d, "
356  "skipping...\\n",
357  sample, i);
358  }
359  }
360 
361  if (debug_mode) {
362  printf("F[%d][%d] sum: %f, count: %d\\n", i, j, sum, count);
363  }
364 
365  if (count > 0) {
366  F[i][j] = sum / count;
367  }
368  }
369  }
370  } else {
371  for (int i = 0; i < n_windows; i++) {
372  std::vector<double> denominator_cache(samples[i].size(), 0.0);
373  for (size_t s = 0; s < samples[i].size(); s++) {
374  double denominator = 0.0;
375  for (int k = 0; k < n_windows; k++) {
376  denominator += window_cache[i][k][s] * z_inv[k];
377  }
378  denominator_cache[s] = 1 / denominator;
379  }
380 
381  for (int j = 0; j < n_windows; j++) {
382  double sum = 0.0;
383  int count = 0;
384 
385  for (size_t s = 0; s < samples[i].size(); s++) {
386  double sample = samples[i][s];
387  double window_j = window_cache[i][j][s];
388  double denominator = denominator_cache[s];
389 
390  if (denominator > 0) {
391  double integrand = (window_j * z_inv[i]) * denominator;
392  sum += integrand;
393  count++;
394  } else if (debug_mode) {
395  printf("Denominator is zero for sample %f in window %d, "
396  "skipping...\\n",
397  sample, i);
398  }
399  }
400 
401  if (debug_mode) {
402  printf("F[%d][%d] sum: %f, count: %d\\n", i, j, sum, count);
403  }
404 
405  if (count > 0) {
406  F[i][j] = sum / count;
407  }
408  }
409  }
410  }
411 
412  return F;
413 }
414 
415 // Z-solver function implementing the fixed point matrix iteration algorithm
416 std::vector<double> zSolver(const std::vector<double> &z_current,
417  const std::vector<WindowConfig> &windows,
418  const std::vector<std::vector<double>> &samples,
419  const std::vector<std::vector<std::vector<double>>> &window_cache,
420  bool use_openmp = true, bool verbose = false,
421  int *total_lines = nullptr) {
422 
423  int n_windows = windows.size();
424  if (verbose && !use_openmp) {
425  std::cout << "Using single-threaded computation for F matrix..."
426  << std::endl;
427  }
428 
429  // F matrix and update z values
430  std::vector<double> z_working = z_current;
431  std::vector<std::vector<double>> F =
432  calcFmatrix(z_working, windows, samples, window_cache, use_openmp);
433 
434  // if (verbose) {
435  // if (total_lines) *total_lines = 1; // Start counting from F matrix
436  // header std::cout << "F matrix:" << std::endl; for (int i = 0; i <
437  // n_windows; i++) {
438  // if (total_lines) (*total_lines)++; // Count each row
439  // std::cout << "[";
440  // for (int j = 0; j < n_windows; j++) {
441  // std::cout << std::setw(10) << std::fixed <<
442  // std::setprecision(5) << F[i][j]; if (j < n_windows - 1)
443  // std::cout << ", ";
444  // }
445  // std::cout << "]" << std::endl;
446  // }
447  // std::cout << std::flush;
448  // }
449 
450  // Compute z_new = z_current * F
451  // could do faster matrix multiplication here?
452  std::vector<double> z_new(n_windows, 0.0);
453  for (int i = 0; i < n_windows; i++) {
454  for (int j = 0; j < n_windows; j++) {
455  z_new[i] += z_current[j] * F[j][i];
456  }
457  }
458 
459  // Normalize z to prevent scale drift and maintain sum(z)=1
460  // TODO: try this with magnitude = 1, the absolute scale doesn't matter but
461  // maybee this condition is preventing elements meeeting their required
462  // values? also just try compleetely disabled
463  // double z_sum = 0.0;
464  // for (int i = 0; i < n_windows; i++) {
465  // z_sum += z_new[i];
466  //}
467  // if (z_sum > 0) {
468  // for (int i = 0; i < n_windows; i++) {
469  // z_new[i] /= z_sum;
470  // }
471  //}
472 
473  // normalise the z values so that magnitude of the vector is 1
474  double z_magnitude = 0.0;
475  for (int i = 0; i < n_windows; i++) {
476  z_magnitude += z_new[i] * z_new[i];
477  }
478  z_magnitude = sqrt(z_magnitude);
479  if (z_magnitude > 0) {
480  for (int i = 0; i < n_windows; i++) {
481  z_new[i] /= z_magnitude;
482  }
483  }
484 
485  return z_new;
486 }
487 
489 
490 // Check convergence
491 bool checkConvergence(const std::vector<double> &z_current, const std::vector<double> &z_prev, double tolerance) {
492  double sum_diffs = 0.0;
493  for (size_t i = 0; i < z_current.size(); i++) {
494  // if (std::abs(z_current[i] - z_prev[i]) > tolerance *
495  // std::max(std::abs(z_current[i]), std::abs(z_prev[i]))) { // intention
496  // here: is the difference between any of the z elements more than the
497  // tolerance*the magnitude of the largest element in z? in this way the
498  // tolerance sets the number of decimal points below the largest element we
499  // are targeting.
500  // return false;
501  // }
502  sum_diffs += std::abs(z_current[i] - z_prev[i]);
503  // check the average difference across all elements instead
504  // of requiring every element to meet the condition, this
505  // should be more robust to individual elements fluctuating
506  // around their target value while the overall z
507  // distribution is still converging
508  if (sum_diffs / z_current.size() > tolerance) { return false;}
509  }
510  return true;
511 }
512 
513 std::vector<double> getZDiffs(const std::vector<double> &z_current, const std::vector<double> &z_prev) {
514  std::vector<double> diffs(z_current.size(), 0.0);
515  for (size_t i = 0; i < z_current.size(); i++) {
516  diffs[i] = std::abs(z_current[i] - z_prev[i]);
517  }
518  return diffs;
519 }
520 
521 // This implements a check on stalling of the evolution of the window weights. Seems to be a better definition for convergence that above
522 // moving-average stalled-convergence check on z values
523 bool checkConvergenceStalled(const std::vector<double> &z_current, const std::vector<double> &z_prev, double tolerance) {
524  (void)z_prev;
525 
526  static std::deque<std::vector<double>> z_history;
527  static std::vector<double> previous_moving_average;
528  static int stagnant_iterations = 0;
529 
530  constexpr int moving_average_window = 500;
531  constexpr int stagnant_required = 500;
532  const double bound = tolerance;
533 
534  if (z_current.empty()) {
535  return false;
536  }
537 
538  // Reset state safely if number of windows changes between solver runs.
539  if (!z_history.empty() && z_history.front().size() != z_current.size()) {
540  z_history.clear();
541  previous_moving_average.clear();
542  stagnant_iterations = 0;
543  }
544 
545  z_history.push_back(z_current);
546  if ((int)z_history.size() > moving_average_window) {
547  z_history.pop_front();
548  }
549 
550  // Wait until the moving-average window is fully populated.
551  if ((int)z_history.size() < moving_average_window) {
552  return false;
553  }
554 
555  std::vector<double> moving_average(z_current.size(), 0.0);
556  for (const auto &z_vec : z_history) {
557  for (size_t i = 0; i < z_vec.size(); i++) {
558  moving_average[i] += z_vec[i];
559  }
560  }
561  for (size_t i = 0; i < moving_average.size(); i++) {
562  moving_average[i] /= moving_average_window;
563  }
564 
565  if (previous_moving_average.empty()) {
566  previous_moving_average = moving_average;
567  return false;
568  }
569 
570  bool all_within_bound = true;
571  for (size_t i = 0; i < moving_average.size(); i++) {
572  if (std::abs(moving_average[i] - previous_moving_average[i]) > bound) {
573  all_within_bound = false;
574  break;
575  }
576  }
577 
578  if (all_within_bound) {
579  stagnant_iterations++;
580  } else {
581  stagnant_iterations = 0;
582  }
583 
584  previous_moving_average = moving_average;
585 
586  if (stagnant_iterations == stagnant_required) {
587  std::cout << "Convergence appears stalled: moving-average change stayed within " << bound << " for " << stagnant_required << " iterations." << std::endl;
588  }
589 
590  return stagnant_iterations >= stagnant_required;
591 }
592 
593 // Main function to run the umbrella sampling solver
594 void UmbrellaSolver(const std::string &config_file = "umbrella_config.yaml") {
595 
596  std::cout << "=== Umbrella Sampling Z-Factor Solver ===" << std::endl;
597 
598  // Debug OpenMP status first
599  std::cout << "Debugging OpenMP availability..." << std::endl;
600 
601 #ifdef MULTITHREAD
602  std::cout << "_OPENMP is defined with value: " << _OPENMP << std::endl;
603  std::cout << "OpenMP version: " << _OPENMP << std::endl;
604  std::cout << "Max threads available: " << omp_get_max_threads() << std::endl;
605 #else
606  std::cout << "_OPENMP is NOT defined - OpenMP not available" << std::endl;
607 #endif
608 
609  std::cout << "Loading configuration from: " << config_file << std::endl;
610 
611  // Parse configuration
612  UmbrellaConfig config = parseYAMLConfig(config_file);
613 
614  if (config.windows.empty() && !config.dynamic_files) {
615  std::cerr << "Error: No windows defined in configuration and dynamic file loading is disabled." << std::endl;
616  return;
617  }
618 
619  std::cout << "Variable of interest: " << config.variable_of_interest << std::endl;
620  std::cout << "Output file: " << config.output_file << std::endl;
621 
622 // Check OpenMP status with detailed debugging
623 #ifdef MULTITHREAD
624  bool openmp_available = true;
625  std::cout << "OpenMP: AVAILABLE" << std::endl;
626  if (config.use_openmp) {
627  std::cout << "OpenMP: ENABLED (using " << omp_get_max_threads() << " threads)" << std::endl;
628  }
629 #else
630  bool openmp_available = false;
631  std::cout << "OpenMP: NOT AVAILABLE" << std::endl;
632  if (config.use_openmp) {
633  std::cout << "OpenMP: NOT AVAILABLE - falling back to single-threaded execution" << std::endl;
634  std::cout << "Note: For OpenMP support, try compiling with: g++ -fopenmp ..." << std::endl;
635  std::cout << "Or ensure OpenMP library is properly loaded in ROOT" << std::endl;
636  config.use_openmp = false;
637  } else {
638  std::cout << "OpenMP: DISABLED (single-threaded execution)" << std::endl;
639  }
640 #endif
641 
642  // Load data from input files
643  std::vector<std::vector<double>> samples; // Declare here to ensure it exists in the scope of the entire
644  // function
645  if (!config.dynamic_files) {
646  samples.resize(config.windows.size());
647  } else {
648  samples.resize(config.dynamic_n_windows);
649  }
650  std::vector<TFile *> input_files;
651  std::vector<TTree *> input_trees;
652 
653  if (!config.dynamic_files) {
654  std::cout << "Using static input files from configuration." << std::endl;
655  for (size_t i = 0; i < config.windows.size(); i++) {
656  std::cout << "Loading file: " << config.windows[i].input_file << std::endl;
657 
658  TFile *file = TFile::Open(config.windows[i].input_file.c_str(), "READ");
659  if (!file || file->IsZombie()) {
660  std::cerr << "Error: Cannot open file " << config.windows[i].input_file << std::endl;
661  continue;
662  }
663 
664  TTree *tree = (TTree *)file->Get("posteriors");
665  if (!tree) {
666  std::cerr << "Error: Cannot find 'posteriors' tree in " << config.windows[i].input_file << std::endl;
667  file->Close();
668  continue;
669  }
670 
671  input_files.push_back(file);
672  input_trees.push_back(tree);
673  }
674  } else {
675  std::cout << "Dynamic file loading enabled. Searching for files in directory: " << config.dynamic_pattern << std::endl;
676  // Use ROOT's TSystem to find all files in the directory
677  TSystemDirectory dir("", config.dynamic_pattern.c_str());
678  TList *files = dir.GetListOfFiles();
679  int file_count = 0;
680  if (files) {
681  TIter next(files);
682  TSystemFile *file;
683  while ((file = (TSystemFile *)next())) {
684  std::string filename = file->GetName();
685  if (filename.find(".root") == std::string::npos) {
686  std::cout << "Skipping non-root file: " << filename << std::endl;
687  continue;
688  }
689 
690  std::string full_path = config.dynamic_pattern + "/" + filename;
691  std::cout << "Found file: " << full_path << std::endl;
692 
693  TFile *root_file = TFile::Open(full_path.c_str(), "READ");
694  if (!root_file || root_file->IsZombie()) {
695  std::cerr << "Error: Cannot open file " << full_path << std::endl;
696  throw std::runtime_error("Cannot open file: " + full_path);
697  }
698 
699  TTree *tree = (TTree *)root_file->Get("posteriors");
700  if (!tree) {
701  std::cerr << "Error: Cannot find 'posteriors' tree in " << full_path << std::endl;
702  root_file->Close();
703  throw std::runtime_error("Missing 'posteriors' tree in file: " + full_path);
704  }
705 
706  file_count++;
707 
708  input_files.push_back(root_file);
709  input_trees.push_back(tree);
710  std::cout << "Loaded tree 'posteriors' from file: " << full_path << std::endl;
711  }
712  } else {
713  std::cerr << "Error: No files found matching pattern: " << config.dynamic_pattern << std::endl;
714  throw std::runtime_error("No files found matching pattern: " + config.dynamic_pattern);
715  }
716 
717  if (file_count != config.dynamic_n_windows) {
718  std::cerr << "Error: Number of files found (" << file_count << ") does not match expected dynamic_n_windows (" << config.dynamic_n_windows << ")." << std::endl;
719  throw std::runtime_error("File count mismatch for dynamic loading.");
720  }
721  }
722 
723  for (size_t i = 0; i < input_trees.size(); i++) {
724  TTree *tree = input_trees[i];
725  TFile *file = input_files[i];
726 
727  std::cout << "\nProcessing file " << i + 1 << "/" << input_trees.size() << ": " << file->GetName() << std::endl;
728 
729  TMacro *macro = (TMacro *)file->Get("MaCh3_Config");
730  if (macro) {
731  std::cout << "Found MaCh3_Config macro in file." << std::endl;
732 
733  // Convert TMacro to YAML: concatenate all lines and parse
734  std::stringstream yaml_text;
735  TList *lines = macro->GetListOfLines();
736  for (int iline = 0; iline < lines->GetEntries(); ++iline) {
737  TObjString *line = (TObjString *)lines->At(iline);
738  yaml_text << line->GetString().Data() << "\n";
739  }
740 
741  try {
742  YAML::Node macro_yaml = YAML::Load(yaml_text.str());
743  YAML::Node umbrellaConfig =
744  macro_yaml["General"]["MCMC"]["Multicanonical"];
745  // Extract window parameters
746  config.windows[i].center = umbrellaConfig["Umbrella"]["UmbrellaMean"].as<double>();
747  std::cout << "Window " << i << " center updated to " << config.windows[i].center << std::endl;
748 
749  // Check if using von Mises distribution
750  std::string biasString = umbrellaConfig["Umbrella"]["UmbrellaBiasFunction"].as<std::string>();
751  M3::BiasFunction biasMode;
752  if (biasString == "gaussian") {
753  biasMode = M3::BiasFunction::kGaussian;
754  std::cout << "Window wieghted with gaussian" << std::endl;
755  } else if (biasString == "generalisedGaussian") {
757  std::cout << "Window weighted with generalised gaussian" << std::endl;
758  } else if (biasString == "vonMises") {
759  biasMode = M3::BiasFunction::kVonMises;
760  std::cout << "Window weighted with vonMises" << std::endl;
761  } else {
762  std::cout << "Unrecognised Bias" << std::endl;
763  }
764  config.windows[i].umbrellaBiasFunction = biasMode;
765 
766  if (config.windows[i].umbrellaBiasFunction == M3::BiasFunction::kVonMises) {
767  // Extract von Mises sigma and compute kappa
768  double vonMises_sigma = umbrellaConfig["Umbrella"]["UmbrellaWidth"].as<double>();
769  config.windows[i].vonMises_kappa = 1.0 / (vonMises_sigma * vonMises_sigma);
770  config.windows[i].width = vonMises_sigma; // Store sigma in width for reference
771  std::cout << "Window " << i << " using von Mises: sigma = " << vonMises_sigma << ", kappa = " << config.windows[i].vonMises_kappa << std::endl;
772  } else {
773  // Extract Gaussian sigma
774  config.windows[i].width = umbrellaConfig["Umbrella"]["UmbrellaWidth"].as<double>();
775  config.windows[i].vonMises_kappa = -1.0; // Not using von Mises
776  std::cout << "Window " << i << " using Gaussian: width = " << config.windows[i].width << std::endl;
777  }
778 
779  } catch (const std::exception &e) {
780  std::cerr << "Warning: Could not parse macro as YAML: " << e.what() << std::endl;
781  }
782  }
783 
784  double var_value;
785  double logL_value;
786  tree->SetBranchAddress(config.variable_of_interest.c_str(), &var_value);
787  tree->SetBranchAddress("LogL", &logL_value);
788 
789  Long64_t nentries = tree->GetEntries();
790  Long64_t filtered_entries = 0;
791  std::cout << "Window " << i << ": " << nentries << " entries" << std::endl;
792 
793  for (Long64_t entry = 0; entry < nentries; entry++) {
794  tree->GetEntry(entry);
795  //if (logL_value > 50.0) { // logl cut no longer needed as the posterior
796  // // chain start has been fixed
797  // filtered_entries++;
798  // continue;
799  //}
800  samples[i].push_back(var_value);
801  }
802  //if (filtered_entries > 0) {
803  // std::cout << "Filtered " << filtered_entries
804  // << " entries with LogL > 500 from window " << i << std::endl;
805  //}
806  }
807 
808  // Sort by window center and keep all per-window containers aligned.
809  // The final weighting stage loops over input_trees by index, so those indices
810  // must track the same sorted window order used by z_current.
811  for (size_t i = 0; i < config.windows.size(); i++) {
812  for (size_t j = i + 1; j < config.windows.size(); j++) {
813  if (config.windows[i].center > config.windows[j].center) {
814  std::swap(config.windows[i], config.windows[j]);
815  std::swap(samples[i], samples[j]);
816  std::swap(input_trees[i], input_trees[j]);
817  std::swap(input_files[i], input_files[j]);
818  }
819  }
820  }
821 
822  // verify the order and file associations after sorting
823  std::cout << "\nFinal window configurations after sorting:" << std::endl;
824  for (size_t i = 0; i < config.windows.size(); i++) {
825  std::cout << "Window " << i << ": center = " << config.windows[i].center
826  << ", width = " << config.windows[i].width
827  << ", vonMises_mode = " << (config.windows[i].umbrellaBiasFunction == M3::BiasFunction::kVonMises ? "Yes" : "No")
828  << ", vonMises_kappa = " << config.windows[i].vonMises_kappa
829  << ", samples = " << samples[i].size() << std::endl;
830  }
831 
832  // Initialize z values
833  std::vector<double> z_current(config.windows.size(), 1.0);
834  std::vector<double> z_prev(config.windows.size(), 1.0);
835 
836  // this should be used to pick up a solve that failed partway through due to reaching iteration max or job cancellation
837  bool hacky_start = false; // Set to true to use the hardcoded starting vector, false to start with all ones
838  if (hacky_start) {
839  // z_current = { 0.02593, 0.02676, 0.02764, 0.03305, 0.03527,
840  // 0.04275, 0.05171, 0.04808, 0.04978, 0.04560, 0.04627,
841  // 0.05187, 0.04993, 0.04656, 0.04690, 0.04954, 0.04940,
842  // 0.04315, 0.03616, 0.02832, 0.01998, 0.01482, 0.01167,
843  // 0.00902, 0.00646, 0.00474, 0.00439, 0.00421, 0.00470,
844  // 0.00553, 0.00626, 0.00822, 0.01070, 0.01477, 0.01750,
845  // 0.02235};
846  z_current = {
847  0.023968629123202176, 0.024927005713178161, 0.026030888791054529,
848  0.036203405237770721, 0.04004944137212621, 0.055993616350153479,
849  0.079251266929094608, 0.065860139904686643, 0.067944181615205768,
850  0.055237804689517243, 0.054778778031073304, 0.066196102148964917,
851  0.059298667596959342, 0.049864361722134341, 0.048890393249559315,
852  0.05284144211204099, 0.050606183191239794, 0.037329936801427918,
853  0.025159187577401387, 0.01487697249782439, 0.0074395236463036573,
854  0.0040535024095969992, 0.0025347709967512371, 0.0015590961074484638,
855  0.00083136243905723782, 0.00047599269828616062, 0.0004454414833514952,
856  0.00045205403592812914, 0.00062098732584897902, 0.00091722187629238541,
857  0.0012407301891732216, 0.0023494590898020503, 0.0041640866720417773,
858  0.0080599626292932776, 0.011429554834689368, 0.018117848911520615};
859  z_prev = z_current; // Start with the same values for previous to avoid
860  // large initial changes
861  std::cout << "!!!!!!!starting from hacky start vector!!!!!!" << std::endl;
862  }
863 
864  std::vector<std::vector<double>> z_evolution;
865 
866  std::cout << "\nStarting iterative z-solver..." << std::endl;
867 
868  // Test OpenMP functionality once before the main loop this can probably be wrapped in debug TODO
869  bool openmp_works = false;
870  if (config.use_openmp) {
871  std::cout << "Testing OpenMP parallelization..." << std::endl;
872  #ifdef MULTITHREAD
873  int max_threads = omp_get_max_threads();
874  #else
875  int max_threads = 1;
876  #endif
877 
878  std::cout << "Max threads reported: " << max_threads << std::endl;
879 
880  // Test parallel region
881  int actual_threads = 1;
882  #ifdef MULTITHREAD
883  #pragma omp parallel
884  #endif
885  {
886  #ifdef MULTITHREAD
887  #pragma omp master
888  #endif
889  {
890  #ifdef MULTITHREAD
891  actual_threads = omp_get_num_threads();
892  #else
893  actual_threads = 1;
894  #endif
895  std::cout << "Actual threads in parallel region: " << actual_threads << std::endl;
896  }
897  }
898 
899  if (actual_threads > 1) {
900  std::cout << "OpenMP is working correctly with " << actual_threads << " threads" << std::endl;
901  openmp_works = true;
902  } else if (max_threads > 1) {
903  std::cout << "WARNING: OpenMP pragmas not working in CLING - falling back to single-threaded" << std::endl;
904  openmp_works = false;
905  } else {
906  openmp_works = false;
907  }
908  }
909 
910  std::cout << "Precomputing window cache..." << std::endl;
911  // TODO: this is slow as hell and causes massive memory usage, whats a smarter
912  // way to do this? break out to a file? RDataFrame?
913  std::vector<std::vector<std::vector<double>>> window_cache = buildWindowCache(config.windows, samples, openmp_works);
914 
915  // TFile to hold the F matrix evolution for the first 15 iterations if needed
916  bool save_matrix = true; // Set to true to enable saving F matrix evolution
917  // Create F_file here to avoid reopening it multiple times in the loop
918  TFile *F_file = nullptr;
919  if (save_matrix) {
920  size_t pos = config.output_file.find(".root");
921  std::string base_name = (pos != std::string::npos) ? config.output_file.substr(0, pos) : config.output_file;
922  F_file = TFile::Open((base_name + "_matrix_evolution.root").c_str(), "RECREATE"); // use name from config with .root subtracted with _matrix_evolution suffix added
923  if (!F_file || F_file->IsZombie()) {
924  std::cerr << "Error: Cannot create file " << base_name + "_matrix_evolution.root" << std::endl;
925  save_matrix = false; // Disable saving if file cannot be created
926  }
927  // add an initial FMatrix with the initial z values for reference
928  std::vector<std::vector<double>> initial_F = calcFmatrix(z_current, config.windows, samples, window_cache, openmp_works);
929  int n_windows = config.windows.size();
930  TH2D initial_F_TH2D("F_matrix_initial", "Initial F matrix;Window j;Window i", n_windows, 0, n_windows, n_windows, 0, n_windows);
931  for (int i = 0; i < n_windows; i++) {
932  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
933  }
934  }
935  F_file->cd();
936  std::cout << "Saving initial F matrix to file..." << std::endl;
937  initial_F_TH2D.Write();
938  }
939 
940  // Timing variables
941  auto start_time = std::chrono::high_resolution_clock::now();
942  auto last_print_time = start_time;
943 
944  bool converged_robustness_check = false; // Flag to indicate if convergence check has been passed at least
945  // once, used to control when to start checking for stalled
946  // convergence
947 
948  if (!converged_robustness_check) {
949  std::cout << "\nStarting iterative solver with convergence checks..." << std::endl;
950  }
951 
952  // Iterative solver
953  int total_output_lines = 0; // Track total lines printed for clearing
954  for (int iteration = 0; iteration < config.max_iterations; iteration++) {
955  if (iteration % config.print_frequency == 0 || iteration == 1) {
956  auto current_time = std::chrono::high_resolution_clock::now();
957 
958  // Clear previous output if not the first iteration
959  if (iteration > 0) {
960  // Move cursor up and clear previous output
961  std::cout << "\033[" << total_output_lines << "A"; // Move up
962  std::cout << "\033[J"; // Clear from cursor to end of screen
963  }
964 
965  // Calculate average relative change (precision metric)
966  double avg_relative_change = 0.0;
967  if (iteration > 0) {
968  for (size_t i = 0; i < z_current.size(); i++) {
969  double rel_change = std::abs(z_current[i] - z_prev[i]) / std::max(std::abs(z_current[i]), 1e-10);
970  avg_relative_change += rel_change;
971  }
972  avg_relative_change /= z_current.size();
973  }
974 
975  // Print z-values
976  std::cout << "Iteration " << std::setw(6) << iteration << ", z values: [";
977  for (size_t i = 0; i < z_current.size(); i++) {
978  std::cout << std::setw(10) << std::fixed << std::setprecision(5) << z_current[i];
979  if (i < z_current.size() - 1)
980  std::cout << ", ";
981  }
982  if (iteration > 0) {
983  auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(current_time - last_print_time);
984  double avg_time_per_iteration = duration.count() / double(config.print_frequency);
985  std::cout << "] (avg: " << std::setw(6) << std::setprecision(1) << avg_time_per_iteration << " ms/iter)" << std::endl;
986  std::cout << "Avg relative change: " << std::scientific << std::setprecision(3) << avg_relative_change << " (target: " << config.tolerance << ")" << std::endl;
987  total_output_lines = 2;
988  } else {
989  std::cout << "]" << std::endl;
990  total_output_lines = 1;
991  }
992 
993  last_print_time = current_time;
994  }
995 
996  z_prev = z_current;
997  z_current = zSolver(z_current, config.windows, samples, window_cache, openmp_works, iteration % config.print_frequency == 0, &total_output_lines);
998  z_evolution.push_back(z_current);
999 
1000  // save the first 15 iterations of the F matrix to check convergence
1001  // behaviour and debug if needed should be a root file with Th2D for easy
1002  // plotting in root, with axes of iteration number and window index, and the
1003  // value being the F matrix element
1004  if (save_matrix && (iteration < 15 || iteration % config.print_frequency == 0)) {
1005  std::vector<std::vector<double>> F_matrix = calcFmatrix(z_current, config.windows, samples, window_cache, openmp_works);
1006  // convert F_matrix to Th2D for saving to root file
1007  int n_windows = config.windows.size();
1008  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);
1009  for (int i = 0; i < n_windows; i++) {
1010  for (int j = 0; j < n_windows; j++) {
1011  F_TH2D.SetBinContent(j + 1, i + 1, F_matrix[i][j]); // Note the order of i and j for correct axis labeling
1012  }
1013  }
1014  // for the purposes of picking back up a solve after it has been
1015  // interrrupted also save the std::vector of z_current. You can put this into hacky start to pick up
1016  TTree *z_tree = new TTree(Form("z_saved_iter_%02d", iteration), Form("Z vector at iteration %02d", iteration));
1017  z_tree->Branch("z_saved", &z_current);
1018  z_tree->Fill();
1019  F_file->cd();
1020  std::cout << "Saving F matrix for iteration " << iteration << " to file..." << std::endl;
1021  F_TH2D.Write();
1022  z_tree->Write();
1023  }
1024  // if (save_matrix && iteration == 15) {
1025  // F_file->Close();
1026  // }
1027 
1028  // after the first convergence check has been passed, randomly perturb the z
1029  // values to see if they return to the same values, this is a robustness
1030  // check to see if the solution is stable or if it is just meeting the
1031  // convergence criteria by chance due to small changes in z values
1032  bool apply_robustness_check = true;
1033  if (iteration % 100 == 0 &&
1034  (checkConvergence(z_current, z_prev, config.tolerance) || checkConvergenceStalled(z_current, z_prev, config.tolerance))) {
1035  if (!converged_robustness_check && apply_robustness_check) {
1036  std::cout << "\nConvergence check passed at iteration " << iteration << ". Starting robustness check with random perturbation..." << std::endl;
1037  converged_robustness_check = true;
1038 
1039  // Apply random perturbation to z_current
1040  std::vector<double> z_perturbed = z_current;
1041  for (size_t i = 0; i < z_perturbed.size(); i++) {
1042  double perturbation = (rand() / RAND_MAX - 0.5) * z_perturbed[i]; // Random perturbation up to 10 times the tolerance
1043  std::cout << "Applying perturbation of " << std::scientific << std::setprecision(6) << perturbation << " to z[" << i << "] = " << std::scientific << std::setprecision(6) << z_perturbed[i] << std::endl;
1044  z_perturbed[i] += perturbation;
1045  if (z_perturbed[i] < 0)
1046  z_perturbed[i] = abs(z_perturbed[i]); // Ensure no negative values
1047  }
1048  z_current = z_perturbed;
1049  std::cout << "\nApplied random perturbation to z values for robustness "
1050  "check.\n" << std::endl;
1051 
1052  } else {
1053  if (checkConvergence(z_current, z_prev, config.tolerance)) {
1054  std::cout << "\nConvergence achieved at iteration " << iteration << std::endl;
1055  } else {
1056  std::cout << "\nConvergence appears to be stalled at iteration " << iteration << std::endl;
1057  }
1058 
1059  if (iteration == config.max_iterations - 1) {
1060  std::cout << "Reached maximum iterations without convergence." << std::endl;
1061  }
1062 
1063  auto end_time = std::chrono::high_resolution_clock::now();
1064  auto total_duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
1065  double avg_time_total = total_duration.count() / double(iteration + 1);
1066  if (save_matrix) {
1067  F_file->Close();
1068  }
1069  std::cout << "\nTerminating at iteration " << iteration << std::endl;
1070  std::cout << "Average time per iteration: " << avg_time_total << " ms" << std::endl;
1071  break;
1072  }
1073  }
1074  }
1075 
1076  std::cout << "\nFinal z values: [";
1077  for (size_t i = 0; i < z_current.size(); i++) {
1078  std::cout << std::fixed << std::setprecision(5) << z_current[i];
1079  if (i < z_current.size() - 1)
1080  std::cout << ", ";
1081  }
1082  std::cout << "]" << std::endl;
1083 
1084  // Create output file
1085  TFile *output_file = TFile::Open(config.output_file.c_str(), "RECREATE");
1086  if (!output_file || output_file->IsZombie()) {
1087  throw std::runtime_error("Cannot create output file: " + config.output_file);
1088  }
1089  output_file->cd();
1090 
1091  // Create combined tree with weights
1092  TTree *combined_tree = new TTree("posteriors", "Combined Posterior Distributions");
1093 
1094  // Variables for the combined tree
1095  double sin2th_12, sin2th_23, sin2th_13, delm2_12, delm2_23, delta_cp;
1096  double baseline, density, LogL, accProb, stepTime;
1097  int step;
1098  std::vector<double> LogL_samples(6);
1099  double LogL_systematic_osc_cov;
1100  double umbrella_weight;
1101  int window_id;
1102 
1103  // TODO: MAJOR make this generic so that we can use systematics aswell
1104  // Set up branches
1105  combined_tree->Branch("sin2th_12", &sin2th_12, "sin2th_12/D");
1106  combined_tree->Branch("sin2th_23", &sin2th_23, "sin2th_23/D");
1107  combined_tree->Branch("sin2th_13", &sin2th_13, "sin2th_13/D");
1108  combined_tree->Branch("delm2_12", &delm2_12, "delm2_12/D");
1109  combined_tree->Branch("delm2_23", &delm2_23, "delm2_23/D");
1110  combined_tree->Branch("delta_cp", &delta_cp, "delta_cp/D");
1111  combined_tree->Branch("baseline", &baseline, "baseline/D");
1112  combined_tree->Branch("density", &density, "density/D");
1113  combined_tree->Branch("LogL", &LogL, "LogL/D");
1114  combined_tree->Branch("accProb", &accProb, "accProb/D");
1115  combined_tree->Branch("step", &step, "step/I");
1116  combined_tree->Branch("stepTime", &stepTime, "stepTime/D");
1117  for (int i = 0; i < 6; i++) {
1118  combined_tree->Branch(Form("LogL_sample_%d", i), &LogL_samples[i], Form("LogL_sample_%d/D", i));
1119  }
1120  combined_tree->Branch("LogL_systematic_osc_cov", &LogL_systematic_osc_cov, "LogL_systematic_osc_cov/D");
1121  combined_tree->Branch("umbrella_weight", &umbrella_weight, "umbrella_weight/D");
1122  combined_tree->Branch("window_id", &window_id, "window_id/I");
1123 
1124  // Fill combined tree
1125  for (size_t i = 0; i < input_trees.size(); i++) {
1126  TTree *tree = input_trees[i];
1127 
1128  // Set branch addresses for reading
1129  tree->SetBranchAddress("sin2th_12", &sin2th_12);
1130  tree->SetBranchAddress("sin2th_23", &sin2th_23);
1131  tree->SetBranchAddress("sin2th_13", &sin2th_13);
1132  tree->SetBranchAddress("delm2_12", &delm2_12);
1133  tree->SetBranchAddress("delm2_23", &delm2_23);
1134  tree->SetBranchAddress("delta_cp", &delta_cp);
1135  tree->SetBranchAddress("baseline", &baseline);
1136  tree->SetBranchAddress("density", &density);
1137  tree->SetBranchAddress("LogL", &LogL);
1138  tree->SetBranchAddress("accProb", &accProb);
1139  tree->SetBranchAddress("step", &step);
1140  tree->SetBranchAddress("stepTime", &stepTime);
1141  for (int j = 0; j < 6; j++) {
1142  tree->SetBranchAddress(Form("LogL_sample_%d", j), &LogL_samples[j]);
1143  }
1144  tree->SetBranchAddress("LogL_systematic_osc_cov", &LogL_systematic_osc_cov);
1145 
1146  Long64_t nentries = tree->GetEntries();
1147 
1148  if (z_current[i] == 0) {
1149  std::cout << "WARNING: Z value for window " << i << " is zero, skipping weighting for this window to avoid division by zero." << std::endl;
1150  }
1151 
1152  for (Long64_t entry = 0; entry < nentries; entry++) {
1153  tree->GetEntry(entry);
1154 
1155  if (z_current[i] == 0) {
1156  umbrella_weight = 0.0; // If z is zero, we cannot apply the umbrella weight, so we set it to 0 (competely downweight this window's contribution)
1157  } else {
1158  // Calculate umbrella weight for this event
1159  // The umbrella weight corrects for the bias introduced by the window function Weight is 1 / sum of all window contributions (equation 4 from paper)
1160  double denominator = 1 / summedWindowsWeighted(delta_cp, config.windows, z_current);
1161 
1162  // umbrella_weight = z_current[i] / denominator; // with or without z_current[i] / denominator? why did I have this originally
1163  umbrella_weight = denominator; // This is the correct form based on the paper - the z_current[i] factor is already included in the summedWindowsWeighted function
1164 
1165  if (combined_tree->Fill() < 0) {
1166  throw std::runtime_error("Failed writing output tree. Check disk quota/space and write permissions for: " + config.output_file);
1167  }
1168  }
1169  }
1170  }
1171 
1172  // Save diagnostics
1173  TCanvas *c1 = new TCanvas("c1", "Z Evolution", 800, 600);
1174 
1175  std::vector<TGraph*> z_graphs(config.windows.size());
1176  TLegend *legend = new TLegend(0.7, 0.7, 0.9, 0.9);
1177 
1178  double ymax = 0.0;
1179  double ymin = std::numeric_limits<double>::max();
1180 
1181  for (size_t i = 0; i < config.windows.size(); i++) {
1182  std::vector<double> iterations, z_vals;
1183  for (size_t j = 0; j < z_evolution.size(); j++) {
1184  iterations.push_back(j);
1185  z_vals.push_back(z_evolution[j][i]);
1186  }
1187 
1188  for (double val : z_vals) {
1189  if (val > ymax)
1190  ymax = val;
1191  if (val < ymin)
1192  ymin = val;
1193  }
1194 
1195  z_graphs[i] = new TGraph(iterations.size(), &iterations[0], &z_vals[0]);
1196  z_graphs[i]->SetLineColor(i + 1);
1197  z_graphs[i]->SetLineWidth(2);
1198  z_graphs[i]->SetName(Form("z_evolution_window_%lu", i));
1199  z_graphs[i]->SetTitle("Evolution of Z Values");
1200 
1201  if (i == 0) {
1202  z_graphs[i]->GetXaxis()->SetTitle("Iteration");
1203  z_graphs[i]->GetYaxis()->SetTitle("Z Value");
1204  z_graphs[i]->Draw("AL");
1205  } else {
1206  z_graphs[i]->Draw("L SAME");
1207  }
1208 
1209  legend->AddEntry(z_graphs[i], Form("Window %lu", i), "l");
1210  z_graphs[i]->Write();
1211  }
1212 
1213  legend->Draw();
1214  z_graphs[0]->SetMaximum(ymax);
1215  z_graphs[0]->SetMinimum(ymin);
1216  c1->Update();
1217  c1->SetLogy();
1218  c1->Write();
1219 
1220  // Write final results
1221  combined_tree->Write();
1222 
1223  // Create summary histogram of delta_cp distribution
1224  TH1D *h_delta_cp = new TH1D("h_delta_cp_weighted", "Weighted Delta CP Distribution", 100, -TMath::Pi(), TMath::Pi());
1225  TH1D *h_delta_cp_unweighted = new TH1D("h_delta_cp_unweighted", "Unweighted Delta CP Distribution", 100, -TMath::Pi(), TMath::Pi());
1226 
1227  combined_tree->Draw("delta_cp>>h_delta_cp_weighted", "umbrella_weight", "goff");
1228  combined_tree->Draw("delta_cp>>h_delta_cp_unweighted", "", "goff");
1229 
1230  h_delta_cp->Write();
1231  h_delta_cp_unweighted->Write();
1232 
1233  // Get entry count before closing the file
1234  Long64_t total_entries = combined_tree->GetEntries();
1235 
1236  output_file->Close();
1237 
1238  // Close input files
1239  for (TFile *file : input_files) {
1240  file->Close();
1241  }
1242 
1243  std::cout << "\nUmbrella sampling outputs created (make sure to check for convergence issues)!" << std::endl;
1244  std::cout << "Output written to: " << config.output_file << std::endl;
1245  std::cout << "Combined tree contains " << total_entries << " entries with umbrella weights." << std::endl;
1246 }
1247 
1248 // Main function for compiled version
1249 int main(int argc, char *argv[]) {
1250  std::string config_file = "umbrella_config.yaml";
1251  if (argc > 1) {
1252  config_file = argv[1];
1253  }
1254 
1255  std::cout << "Running compiled version with OpenMP support" << std::endl;
1256 
1257  try {
1258  UmbrellaSolver(config_file);
1259  } catch (const std::exception &e) {
1260  std::cerr << "Error: " << e.what() << std::endl;
1261  return 1;
1262  }
1263 
1264  // Ensure all OpenMP threads are properly terminated
1265  #ifdef MULTITHREAD
1266  #pragma omp barrier
1267  #endif
1268 
1269  return 0;
1270 }
#define _MaCh3_Safe_Include_Start_
KS: Avoiding warning checking for headers.
Definition: Core.h:126
#define _MaCh3_Safe_Include_End_
std::string config
Definition: ProcessMCMC.cpp:30
std::vector< double > getZDiffs(const std::vector< double > &z_current, const std::vector< double > &z_prev)
int main(int argc, char *argv[])
double summedWindowsWeighted(double x, const std::vector< WindowConfig > &windows, const std::vector< double > &z_values)
double vonMisesWindow(double x, double center, double kappa)
double generalisedGaussian2(double x, double mean, double width)
std::vector< std::vector< double > > calcFmatrix(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, bool use_openmp=true)
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)
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, bool use_openmp=true, bool verbose=false, int *total_lines=nullptr)
bool checkConvergenceStalled(const std::vector< double > &z_current, const std::vector< double > &z_prev, double tolerance)
double GetMulticanonicalWeightGenGaussian(double deltacp, double mean, double width)
std::vector< std::vector< std::vector< double > > > buildWindowCache(const std::vector< WindowConfig > &windows, const std::vector< std::vector< double >> &samples, bool use_openmp=true)
double gaussianWindow(double x, double center, double width)
_MaCh3_Safe_Include_Start_ _MaCh3_Safe_Include_End_ bool debug_mode
void UmbrellaSolver(const std::string &config_file="umbrella_config.yaml")
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.
@ kGaussian
Assumes gaussian prior.
std::string variable_of_interest
std::vector< WindowConfig > windows
std::string output_file
std::string dynamic_pattern
std::string name
std::string input_file
double vonMises_kappa
M3::BiasFunction umbrellaBiasFunction