522 MACH3LOG_INFO(
"Max threads available: {}", omp_get_max_threads());
524 MACH3LOG_WARN(
"_OPENMP is NOT defined - OpenMP not available");
526 MACH3LOG_INFO(
"Loading configuration from: {}", config_file);
532 MACH3LOG_ERROR(
"No windows defined in configuration and dynamic file loading is disabled.");
543 MACH3LOG_INFO(
"OpenMP: ENABLED (using {} threads)", omp_get_max_threads());
548 MACH3LOG_WARN(
"OpenMP: NOT AVAILABLE - falling back to single-threaded execution");
549 MACH3LOG_INFO(
"Note: For OpenMP support, try compiling with: g++ -fopenmp ...");
550 MACH3LOG_INFO(
"Or ensure OpenMP library is properly loaded in ROOT");
553 MACH3LOG_INFO(
"OpenMP: DISABLED (single-threaded execution)");
558 std::vector<std::vector<double>> samples;
561 samples.resize(config.
windows.size());
565 std::vector<TFile *> input_files;
566 std::vector<TTree *> input_trees;
569 MACH3LOG_INFO(
"Using static input files from configuration.");
570 for (
size_t i = 0; i < config.
windows.size(); i++) {
572 TFile *file =
M3::Open(config.
windows[i].input_file.c_str(),
"READ", __FILE__, __LINE__);
574 TTree *tree =
static_cast<TTree*
>(file->Get(
"posteriors"));
581 input_files.push_back(file);
582 input_trees.push_back(tree);
588 TList *files = dir.GetListOfFiles();
593 while ((file =
static_cast<TSystemFile*
>(next()))) {
594 std::string filename = file->GetName();
595 if (filename.find(
".root") == std::string::npos) {
603 TFile *root_file =
M3::Open(full_path,
"READ", __FILE__, __LINE__);
605 TTree *tree =
static_cast<TTree*
>(root_file->Get(
"posteriors"));
609 throw MaCh3Exception(__FILE__, __LINE__,
"Missing 'posteriors' tree in file: " + full_path);
613 input_files.push_back(root_file);
614 input_trees.push_back(tree);
615 MACH3LOG_INFO(
"Loaded tree 'posteriors' from file: {}", full_path);
623 MACH3LOG_ERROR(
"Number of files found ({}) does not match expected dynamic_n_windows ({}).",
625 throw MaCh3Exception(__FILE__, __LINE__,
"File count mismatch for dynamic loading.");
629 for (
size_t i = 0; i < input_trees.size(); i++) {
630 TTree *tree = input_trees[i];
631 TFile *file = input_files[i];
633 MACH3LOG_INFO(
"Processing file {}/{}: {}", i + 1, input_trees.size(), file->GetName());
634 TMacro *macro =
static_cast<TMacro*
>(file->Get(
"MaCh3_Config"));
639 std::stringstream yaml_text;
640 TList *lines = macro->GetListOfLines();
641 for (
int iline = 0; iline < lines->GetEntries(); ++iline) {
642 TObjString *line =
static_cast<TObjString*
>(lines->At(iline));
643 yaml_text << line->GetString().Data() <<
"\n";
647 YAML::Node macro_yaml = YAML::Load(yaml_text.str());
648 YAML::Node umbrellaConfig =
649 macro_yaml[
"General"][
"MCMC"][
"Multicanonical"];
651 config.
windows[i].center = Get<double>(umbrellaConfig[
"Umbrella"][
"UmbrellaMean"], __FILE__, __LINE__);
655 auto biasString = Get<std::string>(umbrellaConfig[
"Umbrella"][
"UmbrellaBiasFunction"], __FILE__, __LINE__);
657 if (biasString ==
"gaussian") {
660 }
else if (biasString ==
"generalisedGaussian") {
663 }
else if (biasString ==
"vonMises") {
670 config.
windows[i].umbrellaBiasFunction = biasMode;
674 double vonMises_sigma = Get<double>(umbrellaConfig[
"Umbrella"][
"UmbrellaWidth"], __FILE__, __LINE__);
675 config.
windows[i].vonMises_kappa = 1.0 / (vonMises_sigma * vonMises_sigma);
676 config.
windows[i].width = vonMises_sigma;
677 MACH3LOG_INFO(
"Window {} using von Mises: sigma = {}, kappa = {}",
678 i, vonMises_sigma, config.
windows[i].vonMises_kappa);
681 config.
windows[i].width = Get<double>(umbrellaConfig[
"Umbrella"][
"UmbrellaWidth"], __FILE__, __LINE__);
682 config.
windows[i].vonMises_kappa = -1.0;
685 }
catch (
const std::exception &e) {
686 MACH3LOG_WARN(
"Could not parse macro as YAML: {}", e.what());
693 tree->SetBranchAddress(
"LogL", &logL_value);
695 Long64_t nentries = tree->GetEntries();
699 for (Long64_t entry = 0; entry < nentries; entry++) {
700 tree->GetEntry(entry);
706 samples[i].push_back(var_value);
717 for (
size_t i = 0; i < config.
windows.size(); i++) {
718 for (
size_t j = i + 1; j < config.
windows.size(); j++) {
721 std::swap(samples[i], samples[j]);
722 std::swap(input_trees[i], input_trees[j]);
723 std::swap(input_files[i], input_files[j]);
730 for (
size_t i = 0; i < config.
windows.size(); i++) {
731 MACH3LOG_INFO(
"Window {}: center = {}, width = {}, vonMises_mode = {}, vonMises_kappa = {}, samples = {}",
734 config.
windows[i].vonMises_kappa, samples[i].size());
738 std::vector<double> z_current(config.
windows.size(), 1.0);
739 std::vector<double> z_prev(config.
windows.size(), 1.0);
742 bool hacky_start =
false;
752 0.023968629123202176, 0.024927005713178161, 0.026030888791054529,
753 0.036203405237770721, 0.04004944137212621, 0.055993616350153479,
754 0.079251266929094608, 0.065860139904686643, 0.067944181615205768,
755 0.055237804689517243, 0.054778778031073304, 0.066196102148964917,
756 0.059298667596959342, 0.049864361722134341, 0.048890393249559315,
757 0.05284144211204099, 0.050606183191239794, 0.037329936801427918,
758 0.025159187577401387, 0.01487697249782439, 0.0074395236463036573,
759 0.0040535024095969992, 0.0025347709967512371, 0.0015590961074484638,
760 0.00083136243905723782, 0.00047599269828616062, 0.0004454414833514952,
761 0.00045205403592812914, 0.00062098732584897902, 0.00091722187629238541,
762 0.0012407301891732216, 0.0023494590898020503, 0.0041640866720417773,
763 0.0080599626292932776, 0.011429554834689368, 0.018117848911520615};
766 MACH3LOG_WARN(
"!!!!!!!starting from hacky start vector!!!!!!");
769 std::vector<std::vector<double>> z_evolution;
773 bool openmp_works =
false;
777 int max_threads = omp_get_max_threads();
785 int actual_threads = 1;
795 actual_threads = omp_get_num_threads();
799 MACH3LOG_INFO(
"Actual threads in parallel region: {}", actual_threads);
803 if (actual_threads > 1) {
804 MACH3LOG_INFO(
"OpenMP is working correctly with {} threads", actual_threads);
807 openmp_works =
false;
814 std::vector<std::vector<std::vector<double>>> window_cache =
buildWindowCache(config.
windows, samples, openmp_works);
817 bool save_matrix =
true;
819 TFile *F_file =
nullptr;
822 std::string base_name = (pos != std::string::npos) ? config.
output_file.substr(0, pos) : config.
output_file;
823 F_file =
TFile::Open((base_name +
"_matrix_evolution.root").c_str(),
"RECREATE");
824 if (!F_file || F_file->IsZombie()) {
825 MACH3LOG_ERROR(
"Cannot create file {}", base_name +
"_matrix_evolution.root");
829 std::vector<std::vector<double>> initial_F =
calcFmatrix(z_current, config.
windows, samples, window_cache);
830 int n_windows =
static_cast<int>(config.
windows.size());
831 TH2D initial_F_TH2D(
"F_matrix_initial",
"Initial F matrix;Window j;Window i", n_windows, 0, n_windows, n_windows, 0, n_windows);
832 for (
int i = 0; i < n_windows; i++) {
833 for (
int j = 0; j < n_windows; j++) {initial_F_TH2D.SetBinContent(j + 1, i + 1, initial_F[i][j]);
838 initial_F_TH2D.Write();
842 auto start_time = std::chrono::high_resolution_clock::now();
843 auto last_print_time = start_time;
845 bool converged_robustness_check =
false;
849 if (!converged_robustness_check) {
850 MACH3LOG_INFO(
"Starting iterative solver with convergence checks...");
854 int total_output_lines = 0;
855 for (
int iteration = 0; iteration < config.
max_iterations; iteration++) {
857 auto current_time = std::chrono::high_resolution_clock::now();
862 std::cout <<
"\033[" << total_output_lines <<
"A";
863 std::cout <<
"\033[J";
867 double avg_relative_change = 0.0;
869 for (
size_t i = 0; i < z_current.size(); i++) {
870 double rel_change = std::abs(z_current[i] - z_prev[i]) / std::max(std::abs(z_current[i]), 1e-10);
871 avg_relative_change += rel_change;
873 avg_relative_change /=
static_cast<double>(z_current.size());
877 std::cout <<
"Iteration " << std::setw(6) << iteration <<
", z values: [";
878 for (
size_t i = 0; i < z_current.size(); i++) {
879 std::cout << std::setw(10) << std::fixed << std::setprecision(5) << z_current[i];
880 if (i < z_current.size() - 1)
884 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(current_time - last_print_time);
885 double avg_time_per_iteration =
static_cast<double>(duration.count()) /
887 std::cout <<
"] (avg: " << std::setw(6) << std::setprecision(1) << avg_time_per_iteration <<
" ms/iter)" << std::endl;
888 std::cout <<
"Avg relative change: " << std::scientific << std::setprecision(3) << avg_relative_change <<
" (target: " << config.
tolerance <<
")" << std::endl;
889 total_output_lines = 2;
891 std::cout <<
"]" << std::endl;
892 total_output_lines = 1;
894 last_print_time = current_time;
898 z_current =
zSolver(z_current, config.
windows, samples, window_cache, openmp_works, iteration % config.
print_frequency == 0, &total_output_lines);
899 z_evolution.push_back(z_current);
905 if (save_matrix && (iteration < 15 || iteration % config.
print_frequency == 0)) {
906 std::vector<std::vector<double>> F_matrix =
calcFmatrix(z_current, config.
windows, samples, window_cache);
908 int n_windows =
static_cast<int>(config.
windows.size());
909 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);
910 for (
int i = 0; i < n_windows; i++) {
911 for (
int j = 0; j < n_windows; j++) {
912 F_TH2D.SetBinContent(j + 1, i + 1, F_matrix[i][j]);
917 TTree *z_tree =
new TTree(Form(
"z_saved_iter_%02d", iteration), Form(
"Z vector at iteration %02d", iteration));
918 z_tree->Branch(
"z_saved", &z_current);
921 MACH3LOG_INFO(
"Saving F matrix for iteration {} to file...", iteration);
933 bool apply_robustness_check =
true;
934 if (iteration % 100 == 0 &&
936 if (!converged_robustness_check && apply_robustness_check) {
937 MACH3LOG_INFO(
"Convergence check passed at iteration {}. Starting robustness check with random perturbation...", iteration);
938 converged_robustness_check =
true;
941 std::vector<double> z_perturbed = z_current;
942 for (
size_t i = 0; i < z_perturbed.size(); i++) {
944 double perturbation = gRandom3.Uniform(-0.5, 0.5) * z_perturbed[i];
945 MACH3LOG_INFO(
"Applying perturbation of {:.6e} to z[{}] = {:.6e}", perturbation, i, z_perturbed[i]);
946 z_perturbed[i] += perturbation;
947 if (z_perturbed[i] < 0)
948 z_perturbed[i] = abs(z_perturbed[i]);
950 z_current = z_perturbed;
951 MACH3LOG_INFO(
"Applied random perturbation to z values for robustness check.");
954 MACH3LOG_INFO(
"Convergence achieved at iteration {}", iteration);
956 MACH3LOG_WARN(
"Convergence appears to be stalled at iteration {}", iteration);
960 MACH3LOG_WARN(
"Reached maximum iterations without convergence.");
963 auto end_time = std::chrono::high_resolution_clock::now();
964 auto total_duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
965 double avg_time_total =
static_cast<double>(total_duration.count()) /
966 static_cast<double>(iteration + 1);
971 MACH3LOG_INFO(
"Average time per iteration: {} ms", avg_time_total);
977 std::ostringstream oss;
978 oss <<
"Final z values: [";
979 for (
size_t i = 0; i < z_current.size(); i++) {
980 oss << std::fixed << std::setprecision(5) << z_current[i];
981 if (i < z_current.size() - 1)
987 std::filesystem::copy_file(input_files[0]->GetName(), config.
output_file,
988 std::filesystem::copy_options::overwrite_existing);
993 TTree *input_tree =
dynamic_cast<TTree*
>(output_file->Get(
"posteriors"));
995 TTree *combined_tree = input_tree->CloneTree(0);
998 double umbrella_weight;
1002 combined_tree->Branch(
"umbrella_weight", &umbrella_weight,
"umbrella_weight/D");
1003 combined_tree->Branch(
"window_id", &window_id,
"window_id/I");
1006 for (
size_t i = 0; i < input_trees.size(); i++) {
1007 TTree *tree = input_trees[i];
1009 Long64_t nentries = tree->GetEntries();
1012 int oldLevel = gErrorIgnoreLevel;
1013 gErrorIgnoreLevel = kError;
1014 combined_tree->CopyAddresses(tree);
1015 gErrorIgnoreLevel = oldLevel;
1018 tree->SetBranchAddress(
"delta_cp", &delta_cp);
1021 combined_tree->GetBranch(
"delta_cp")->SetAddress(&delta_cp);
1022 if (z_current[i] == 0) {
1023 MACH3LOG_WARN(
"Z value for window {} is zero, skipping weighting for this window to avoid division by zero.", i);
1025 window_id =
static_cast<int>(i);
1027 for (Long64_t entry = 0; entry < nentries; entry++) {
1028 tree->GetEntry(entry);
1030 if (z_current[i] == 0) {
1031 umbrella_weight = 0.0;
1038 umbrella_weight = denominator;
1041 if (combined_tree->Fill() < 0) {
1042 MACH3LOG_WARN(
"Failed writing output tree. Check disk quota/space and write permissions for: {}", config.
output_file);
1048 combined_tree->Write(input_tree->GetName(), TObject::kOverwrite);
1050 TDirectory* UmbreallaDir = output_file->mkdir(
"Umbrealla");
1053 TCanvas c1(
"c1",
"Z Evolution", 800, 600);
1054 std::vector<TGraph*> z_graphs(config.
windows.size());
1055 TLegend legend(0.7, 0.7, 0.9, 0.9);
1058 double ymin = std::numeric_limits<double>::max();
1060 for (
size_t i = 0; i < config.
windows.size(); i++) {
1061 std::vector<double> iterations, z_vals;
1062 for (
size_t j = 0; j < z_evolution.size(); j++) {
1063 iterations.push_back(
static_cast<int>(j));
1064 z_vals.push_back(z_evolution[j][i]);
1067 for (
double val : z_vals) {
1074 z_graphs[i] =
new TGraph(
static_cast<int>(iterations.size()), &iterations[0], &z_vals[0]);
1075 z_graphs[i]->SetLineColor(
static_cast<Color_t
>(i + 1));
1076 z_graphs[i]->SetLineWidth(2);
1077 z_graphs[i]->SetName(Form(
"z_evolution_window_%lu", i));
1078 z_graphs[i]->SetTitle(
"Evolution of Z Values");
1081 z_graphs[i]->GetXaxis()->SetTitle(
"Iteration");
1082 z_graphs[i]->GetYaxis()->SetTitle(
"Z Value");
1083 z_graphs[i]->Draw(
"AL");
1085 z_graphs[i]->Draw(
"L SAME");
1088 legend.AddEntry(z_graphs[i], Form(
"Window %lu", i),
"l");
1089 z_graphs[i]->Write();
1093 z_graphs[0]->SetMaximum(ymax);
1094 z_graphs[0]->SetMinimum(ymin);
1100 TH1D *h_delta_cp =
new TH1D(
"h_delta_cp_weighted",
"Weighted Delta CP Distribution", 100, -TMath::Pi(), TMath::Pi());
1101 TH1D *h_delta_cp_unweighted =
new TH1D(
"h_delta_cp_unweighted",
"Unweighted Delta CP Distribution", 100, -TMath::Pi(), TMath::Pi());
1103 combined_tree->Draw(
"delta_cp>>h_delta_cp_weighted",
"umbrella_weight",
"goff");
1104 combined_tree->Draw(
"delta_cp>>h_delta_cp_unweighted",
"",
"goff");
1106 h_delta_cp->Write();
1107 h_delta_cp_unweighted->Write();
1109 UmbreallaDir->Close();
1110 delete UmbreallaDir;
1113 Long64_t total_entries = combined_tree->GetEntries();
1118 YAML::Node umbrella_config;
1119 umbrella_config[
"UmbrellaSolver"] = yaml_config[
"UmbrellaSolver"];
1122 TMacro UmbrellaHeader =
YAMLtoTMacro(umbrella_config,
"Umbrella_Config");
1123 UmbrellaHeader.Write();
1125 output_file->Close();
1128 for (TFile *file : input_files) {
1132 MACH3LOG_INFO(
"Umbrella sampling outputs created (make sure to check for convergence issues)!");
1134 MACH3LOG_INFO(
"Combined tree contains {} entries with umbrella weights.", total_entries);
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, bool use_openmp=true)
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, [[maybe_unused]] int *total_lines=nullptr)
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)
TMacro YAMLtoTMacro(const YAML::Node &yaml_node, const std::string &name)
Convert a YAML node to a ROOT TMacro object.
#define M3OpenConfig(filename)
Macro to simplify calling LoadYaml with file and line info.