MaCh3  2.8.0
Reference Guide
Classes | Functions | Variables
UmbrellaSolver.cpp File Reference
#include <filesystem>
#include "Fitters/Processing/MCMCProcessor.h"
#include "Manager/Manager.h"
#include "Fitters/Algorithms/MulticanonicalMCMCHandler.h"
#include "TSystem.h"
#include "TChain.h"
#include "TSystemDirectory.h"
Include dependency graph for UmbrellaSolver.cpp:

Go to the source code of this file.

Classes

struct  WindowConfig
 
struct  UmbrellaConfig
 

Functions

UmbrellaConfig parseYAMLConfig (const std::string &filename)
 YAML-based config parser using yaml-cpp library. More...
 
double gaussianWindow (double x, double center, double width)
 
double vonMisesWindow (double x, double center, double kappa)
 
double generalisedGaussian2 (double x, double mean, double width)
 
double GetMulticanonicalWeightGenGaussian (double deltacp, double mean, double width, double multicanonicalBeta)
 
double summedWindowsWeighted (double x, const std::vector< WindowConfig > &windows, const std::vector< double > &z_values)
 
size_t calculateWindowCacheSizeBytes (const std::vector< WindowConfig > &windows, const std::vector< std::vector< double >> &samples)
 
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)
 
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)
 
bool checkConvergence (const std::vector< double > &z_current, const std::vector< double > &z_prev, double tolerance)
 A few different convergence checks. More...
 
std::vector< double > getZDiffs (const std::vector< double > &z_current, const std::vector< double > &z_prev)
 
bool checkConvergenceStalled (const std::vector< double > &z_current, const std::vector< double > &z_prev, double tolerance)
 
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)
 
void UmbrellaSolver (const std::string &config_file)
 
int main (int argc, char *argv[])
 

Variables

_MaCh3_Safe_Include_Start_ _MaCh3_Safe_Include_End_ bool debug_mode = false
 

Detailed Description

Author
David Riley

Definition in file UmbrellaSolver.cpp.

Function Documentation

◆ buildWindowCache()

std::vector<std::vector<std::vector<double> > > buildWindowCache ( const std::vector< WindowConfig > &  windows,
const std::vector< std::vector< double >> &  samples 
)

Definition at line 175 of file UmbrellaSolver.cpp.

176  {
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 }
#define MACH3LOG_ERROR
Definition: MaCh3Logger.h:37
#define MACH3LOG_INFO
Definition: MaCh3Logger.h:35
#define MACH3LOG_WARN
Definition: MaCh3Logger.h:36
double vonMisesWindow(double x, double center, double kappa)
double GetMulticanonicalWeightGenGaussian(double deltacp, double mean, double width, double multicanonicalBeta)
double gaussianWindow(double x, double center, double width)
_MaCh3_Safe_Include_Start_ _MaCh3_Safe_Include_End_ bool debug_mode
size_t calculateWindowCacheSizeBytes(const std::vector< WindowConfig > &windows, const std::vector< std::vector< double >> &samples)
Custom exception class used throughout MaCh3.
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.

◆ calcFmatrix()

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 
)

Definition at line 262 of file UmbrellaSolver.cpp.

265  {
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 }

◆ calculateWindowCacheSizeBytes()

size_t calculateWindowCacheSizeBytes ( const std::vector< WindowConfig > &  windows,
const std::vector< std::vector< double >> &  samples 
)

Definition at line 163 of file UmbrellaSolver.cpp.

163  {
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 }

◆ checkConvergence()

bool checkConvergence ( const std::vector< double > &  z_current,
const std::vector< double > &  z_prev,
double  tolerance 
)

A few different convergence checks.

Definition at line 375 of file UmbrellaSolver.cpp.

375  {
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 }

◆ checkConvergenceStalled()

bool checkConvergenceStalled ( const std::vector< double > &  z_current,
const std::vector< double > &  z_prev,
double  tolerance 
)

Definition at line 409 of file UmbrellaSolver.cpp.

409  {
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 }

◆ DiagnoseUmbrella()

void DiagnoseUmbrella ( const UmbrellaConfig &  config,
TFile *  output_file,
const std::vector< std::vector< double >> &  z_evolution,
TTree *  combined_tree 
)

Definition at line 663 of file UmbrellaSolver.cpp.

664  {
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 }
std::string variable_of_interest
std::vector< WindowConfig > windows

◆ gaussianWindow()

double gaussianWindow ( double  x,
double  center,
double  width 
)

Definition at line 102 of file UmbrellaSolver.cpp.

102  {
103  return exp(-0.5 * pow((x - center) / width, 2)) / (width * sqrt(2 * TMath::Pi()));
104 }

◆ generalisedGaussian2()

double generalisedGaussian2 ( double  x,
double  mean,
double  width 
)

Definition at line 122 of file UmbrellaSolver.cpp.

122  {
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 }
constexpr double UmbrellaGaussianNormFactor

◆ GetMulticanonicalWeightGenGaussian()

double GetMulticanonicalWeightGenGaussian ( double  deltacp,
double  mean,
double  width,
double  multicanonicalBeta 
)
Todo:
modify this to instead use atan2 implementation for wrapping

Definition at line 132 of file UmbrellaSolver.cpp.

132  {
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 }
double generalisedGaussian2(double x, double mean, double width)

◆ getZDiffs()

std::vector<double> getZDiffs ( const std::vector< double > &  z_current,
const std::vector< double > &  z_prev 
)

Definition at line 399 of file UmbrellaSolver.cpp.

399  {
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 }

◆ LoadFiles()

void LoadFiles ( std::vector< std::vector< double >> &  samples,
UmbrellaConfig &  config,
std::vector< TFile * > &  input_files,
std::vector< TTree * > &  input_trees 
)

Definition at line 480 of file UmbrellaSolver.cpp.

480  {
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 }
#define MACH3LOG_DEBUG
Definition: MaCh3Logger.h:34
Creates fancy formatted text tables for terminal output.
Definition: TablePrinter.h:50
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.
M3::BiasFunction ParseBiasFunction(const std::string &biasFunctionName)
std::string BiasFunction_ToString(const M3::BiasFunction &biasFunction)
std::string dynamic_pattern

◆ main()

int main ( int  argc,
char *  argv[] 
)

Definition at line 1035 of file UmbrellaSolver.cpp.

1035  {
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 }
void SetMaCh3LoggerFormat()
Set messaging format of the logger.
Definition: MaCh3Logger.h:60
void UmbrellaSolver(const std::string &config_file)

◆ parseYAMLConfig()

UmbrellaConfig parseYAMLConfig ( const std::string &  filename)

YAML-based config parser using yaml-cpp library.

Definition at line 45 of file UmbrellaSolver.cpp.

45  {
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 }
std::string output_file
std::string name

◆ summedWindowsWeighted()

double summedWindowsWeighted ( double  x,
const std::vector< WindowConfig > &  windows,
const std::vector< double > &  z_values 
)

Definition at line 144 of file UmbrellaSolver.cpp.

144  {
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 }

◆ UmbrellaSolver()

void UmbrellaSolver ( const std::string &  config_file)

Definition at line 730 of file UmbrellaSolver.cpp.

730  {
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 }
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)
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)
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)
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
double Uniform(const double min=0.0, const double max=1.0)
Generate a uniform random number in [min, max).
Definition: Random.h:116
int GetNThreads()
number of threads which we need for example for TRandom3
Definition: Monitor.cpp:372

◆ vonMisesWindow()

double vonMisesWindow ( double  x,
double  center,
double  kappa 
)

Definition at line 107 of file UmbrellaSolver.cpp.

107  {
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 }

◆ zSolver()

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 
)

Normalize z to prevent scale drift and maintain sum(z)=1

Todo:
: try this with magnitude = 1, the absolute scale doesn't matter but maybee this condition is preventing elements meeting their required values? also just try completely disabled double z_sum = 0.0; for (int i = 0; i < n_windows; i++) { z_sum += z_new[i]; } if (z_sum > 0) { for (int i = 0; i < n_windows; i++) { z_new[i] /= z_sum; }

Definition at line 325 of file UmbrellaSolver.cpp.

330  {
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 }

Variable Documentation

◆ debug_mode

Definition at line 17 of file UmbrellaSolver.cpp.