From c1ddee24c4c7ac565bf415992b5cac22b7f765d0 Mon Sep 17 00:00:00 2001 From: nathanielerowe <70993723+nathanielerowe@users.noreply.github.com> Date: Fri, 16 Oct 2026 11:17:23 -0500 Subject: [PATCH 1/4] Add model compatibility for data --- bin/PROfit_cli.cxx | 2 +- bin/PROfit_setup.cxx | 82 ++++++++++++++++++++++++++++++++++++++++++-- inc/PROconfig.h | 4 +++ src/PROconfig.cxx | 24 +++++++++++++ 4 files changed, 109 insertions(+), 3 deletions(-) diff --git a/bin/PROfit_cli.cxx b/bin/PROfit_cli.cxx index 01056565..1c574b5d 100644 --- a/bin/PROfit_cli.cxx +++ b/bin/PROfit_cli.cxx @@ -15,7 +15,7 @@ PROpt::PROpt(int argc, char **argv) { app.add_option("--syst-throws", syst_throws, "Number of random throws per spline when converting splines to covariance matrices (spline_to_covariance systematics, and the plot fractional-systematics/covariance breakdowns). Default 500.")->check(CLI::PositiveNumber); app.add_option("-c, --chi2", chi2, "Which chi2 function to use. Options: neyman (default; stat cov = diag(data)), pearson (stat cov = diag(prediction)), CNP, poisson. Legacy aliases PROchi, PROCNP, Poisson accepted.")->default_str("neyman"); app.add_option("-d, --data", data_xml, "Load from a seperate data xml/data file instead of signal injection. Only used with plot subcommand.")->default_str(""); - app.add_option("-i, --inject", fake_data_osc_params, "Physics parameters to inject as fake-data true signal. Example: dmsq 3 sinsq2thmm 0.25")->expected(-1); + app.add_option("-i, --inject", fake_data_osc_params, "Physics parameters to inject as fake-data true signal. Example: dmsq 3 sinsq2thmm 0.25. With --data (or an embedded ) whose config has a , the data events are reweighted at this point instead.")->expected(-1); app.add_option("--inject-cv", cv_osc_params, "Physics parameters to inject as CV. Example: dmsq 3 sinsq2thmm 0.25")->expected(-1); app.add_option("--fix", fixed_params, "Fix Certain Physics or Systematics parameters. Fixed to CV."); app.add_option("-s, --seed", global_seed, "A global seed for PROseed rng. Default to -1 for hardware rng seed.")->default_val(-1); diff --git a/bin/PROfit_setup.cxx b/bin/PROfit_setup.cxx index 164d6d5c..7fa52441 100644 --- a/bin/PROfit_setup.cxx +++ b/bin/PROfit_setup.cxx @@ -14,6 +14,75 @@ static int find_spline_index(const PROsyst &systs, const PROconfig &config, cons return it == systs.spline_names.end() ? -1 : (int)std::distance(systs.spline_names.begin(), it); } +// --data + --inject: when the data config carries a , process the data files like MC +// (a PROpeller with per-event truth/model_rule; no systematics) and build the data spectrum +// at the injected physics point. The physics values come from the MC-side fakedataparams, +// matched to the data model by parameter NAME (so the data model may be a different tag or +// order); data-model parameters not given via --inject sit at the data model's default. +static void build_injected_data(std::vector &variable_data, const PROconfig &config, const PROconfig &dataconfig, + const PROmodel &model, const Eigen::VectorXf &fakedataparams, const PROpt &options, + std::uniform_int_distribution &dseed) { + const std::string dataPropBinName = options.analysis_tag + "_data_prop.bin"; + PROpeller dataprop; + if((*options.process_command) || !std::filesystem::exists(dataPropBinName)) { + log(L"%1% || --inject with a data : processing data files into a PROpeller, saving to %2%") % __func__ % dataPropBinName.c_str(); + std::vector> datasysts; // data carries no systematics + PROcess_CAFAna(dataconfig, datasysts, dataprop, options.noxrootd); + dataprop.save(dataPropBinName); + } else { + log(L"%1% || --inject with a data : loading data PROpeller from %2%") % __func__ % dataPropBinName.c_str(); + dataprop.load(dataPropBinName); + if(dataconfig.hash != dataprop.hash) { + if(options.force) { + log(L"%1% || WARNING data config hash (%2%) and binary data PROpeller hash (%3%) not compatible! Forcing ahead.") % __func__ % dataconfig.hash % dataprop.hash; + } else { + log(L"%1% || ERROR data config hash (%2%) and binary data PROpeller hash (%3%) not compatible! Rerun with process.") % __func__ % dataconfig.hash % dataprop.hash; + exit(EXIT_FAILURE); + } + } + } + + std::unique_ptr datamodel = get_model_from_string(dataconfig, dataprop); + + // Every --inject name must exist in the data model, otherwise the injection would be + // silently dropped from the data. + for(const auto &[name, value]: options.fake_data_osc_params) { + if(std::find(datamodel->param_names.begin(), datamodel->param_names.end(), name) == datamodel->param_names.end()) { + log(L"%1% || --inject parameter %2% is not a parameter of the data model (tag %3%). Valid names are %4%. Terminating.") + % __func__ % name.c_str() % dataconfig.m_model_tag.c_str() % datamodel->param_names; + exit(EXIT_FAILURE); + } + } + + Eigen::VectorXf dataparams = datamodel->default_val; + for(size_t j = 0; j < datamodel->nparams; ++j) { + const auto it = std::find(model.param_names.begin(), model.param_names.end(), datamodel->param_names[j]); + if(it == model.param_names.end()) { + log(L"%1% || Data model parameter %2% is not in the MC model; using the data model default %3%.") + % __func__ % datamodel->param_names[j].c_str() % dataparams(j); + continue; + } + const size_t k = std::distance(model.param_names.begin(), it); + // fakedataparams is in the MC model's internal (possibly log10) units; convert via + // linear in case the two models disagree on is_log10 for this name. + const float lin = model.is_log10[k] ? std::pow(10.0f, fakedataparams(k)) : fakedataparams(k); + dataparams(j) = datamodel->is_log10[j] ? std::log10(lin) : lin; + log(L"%1% || Data injected parameter %2% = %3% (internally %4%)") % __func__ % datamodel->param_names[j].c_str() % lin % dataparams(j); + } + + PROsyst emptySyst; + for(size_t io = 0; io < dataconfig.m_num_variables; ++io) { + const bool fill_this_variable = config.m_channel_variable_plot_bool.at(io) || io == config.i_prime; + PROspec data_spec = fill_this_variable + ? FillSpectra(dataconfig, dataprop, emptySyst, *datamodel, dataparams, !options.eventbyevent, io) + : PROspec(dataconfig.m_num_variable_bins_total[io]); + if(options.poisson_throw) data_spec = PROspec::PoissonVariation(data_spec, dseed(PROseed::global_rng)); + Eigen::VectorXf data_vec = CollapseMatrix(dataconfig, data_spec.Spec(), io); + variable_data.push_back(PROdata(data_vec, data_vec.array().sqrt())); + } + log(L"%1% || Built data spectrum with injected physics from %2% data events.") % __func__ % dataprop.NEvent(); +} + PROdata construct_data(std::vector &variable_data, bool use_real_data, const PROconfig &config, const PROpeller &prop, const PROmodel &model, const std::vector &variable_systs, const Eigen::VectorXf &fakedataparams, const PROpt &options) { PROdata data; std::uniform_int_distribution dseed(0, std::numeric_limits::max()); @@ -29,7 +98,7 @@ PROdata construct_data(std::vector &variable_data, bool use_real_data, log(L"%1% || Using embedded section from XML for data config") % __func__; dataconfig = config.BuildDataConfig(); } - std::string dataBinName = options.analysis_tag+"_data.bin"; + for(size_t i = 0; i < dataconfig.m_num_channels; ++i) { size_t nsubch = dataconfig.m_num_subchannels[i]; if(nsubch != 1) { @@ -53,7 +122,16 @@ PROdata construct_data(std::vector &variable_data, bool use_real_data, exit(EXIT_FAILURE); } - if((*options.process_command) || (!std::filesystem::exists(dataBinName)) ){ + const bool inject_requested = !options.fake_data_osc_params.empty(); + if(inject_requested && !dataconfig.m_has_model) { + log(L"%1% || WARNING: --inject was given with real data, but the data config has no , so the injection is NOT applied to the data. " + L"Add a to the data XML (or for an embedded section) to enable it.") % __func__; + } + + std::string dataBinName = options.analysis_tag+"_data.bin"; + if(inject_requested && dataconfig.m_has_model) { + build_injected_data(variable_data, config, dataconfig, model, fakedataparams, options, dseed); + } else if((*options.process_command) || (!std::filesystem::exists(dataBinName)) ){ log(L"%1% || Processing Data Spectrum and saving to binary output also: %2%") % __func__ % dataBinName.c_str(); //Process the CAF files to grab and fill spectrum directly diff --git a/inc/PROconfig.h b/inc/PROconfig.h index 2fd499e8..c2a76200 100644 --- a/inc/PROconfig.h +++ b/inc/PROconfig.h @@ -581,6 +581,10 @@ namespace PROfit{ //Some model infomation std::string m_model_tag; + /// True when a element was parsed from this XML. For a data config + /// (from --data or an embedded section) this enables --inject to + /// reweight the data events at the injected physics point. + bool m_has_model = false; std::vector m_model_rule_index; std::vector m_model_rule_names; std::vector m_model_parameter_index; diff --git a/src/PROconfig.cxx b/src/PROconfig.cxx index ceb2e03e..f4fcce2c 100644 --- a/src/PROconfig.cxx +++ b/src/PROconfig.cxx @@ -2166,6 +2166,7 @@ int PROconfig::LoadFromXML(const std::string &filename){ pModel = doc.FirstChildElement("model"); if(pModel){ + m_has_model = true; // Read in how many bins this channel uses const std::vector expected_attrs = {"tag","name","index"}; @@ -2441,6 +2442,29 @@ int PROconfig::LoadFromXML(const std::string &filename){ } dataXml << "\n"; + // Optional model for the data, so --inject can reweight data events (e.g. an MC + // fake-data sample carrying truth info). This is opt-in and explicit, because real + // data branches commonly carry a boilerplate model_rule="0": + // * a element inside is used as-is, or + // * reuses the top-level . + // With neither, the data XML is byte-identical to before (no model). + { + tinyxml2::XMLElement* pDataModel = pData->FirstChildElement("model"); + if(!pDataModel && pData->BoolAttribute("inherit_model", false)) { + pDataModel = doc.FirstChildElement("model"); + if(!pDataModel) { + log(L"%1% || but there is no top-level to inherit.") % __func__; + throw std::invalid_argument(" requires a top-level "); + } + log(L"%1% || inherits the top-level (inherit_model=\"true\").") % __func__; + } + if(pDataModel) { + tinyxml2::XMLPrinter printer; + pDataModel->Accept(&printer); + dataXml << printer.CStr() << "\n"; + } + } + // Serialize MCFile elements from inside the section tinyxml2::XMLElement* pDataMC = pData->FirstChildElement("MCFile"); while(pDataMC) { From ebc91207a56ad87fb03bc511a04b16bfcc752966 Mon Sep 17 00:00:00 2001 From: nathanielerowe <70993723+nathanielerowe@users.noreply.github.com> Date: Fri, 16 Oct 2026 11:58:21 -0500 Subject: [PATCH 2/4] Cleaning --- bin/PROfit_cli.cxx | 2 +- bin/PROfit_setup.cxx | 87 +++++++------------ inc/PROconfig.h | 5 +- quick.patch | 201 +++++++++++++++++++++++++++++++++++++++++++ src/PROconfig.cxx | 31 +++---- 5 files changed, 242 insertions(+), 84 deletions(-) create mode 100644 quick.patch diff --git a/bin/PROfit_cli.cxx b/bin/PROfit_cli.cxx index 1c574b5d..01056565 100644 --- a/bin/PROfit_cli.cxx +++ b/bin/PROfit_cli.cxx @@ -15,7 +15,7 @@ PROpt::PROpt(int argc, char **argv) { app.add_option("--syst-throws", syst_throws, "Number of random throws per spline when converting splines to covariance matrices (spline_to_covariance systematics, and the plot fractional-systematics/covariance breakdowns). Default 500.")->check(CLI::PositiveNumber); app.add_option("-c, --chi2", chi2, "Which chi2 function to use. Options: neyman (default; stat cov = diag(data)), pearson (stat cov = diag(prediction)), CNP, poisson. Legacy aliases PROchi, PROCNP, Poisson accepted.")->default_str("neyman"); app.add_option("-d, --data", data_xml, "Load from a seperate data xml/data file instead of signal injection. Only used with plot subcommand.")->default_str(""); - app.add_option("-i, --inject", fake_data_osc_params, "Physics parameters to inject as fake-data true signal. Example: dmsq 3 sinsq2thmm 0.25. With --data (or an embedded ) whose config has a , the data events are reweighted at this point instead.")->expected(-1); + app.add_option("-i, --inject", fake_data_osc_params, "Physics parameters to inject as fake-data true signal. Example: dmsq 3 sinsq2thmm 0.25")->expected(-1); app.add_option("--inject-cv", cv_osc_params, "Physics parameters to inject as CV. Example: dmsq 3 sinsq2thmm 0.25")->expected(-1); app.add_option("--fix", fixed_params, "Fix Certain Physics or Systematics parameters. Fixed to CV."); app.add_option("-s, --seed", global_seed, "A global seed for PROseed rng. Default to -1 for hardware rng seed.")->default_val(-1); diff --git a/bin/PROfit_setup.cxx b/bin/PROfit_setup.cxx index 7fa52441..35dcecce 100644 --- a/bin/PROfit_setup.cxx +++ b/bin/PROfit_setup.cxx @@ -14,73 +14,48 @@ static int find_spline_index(const PROsyst &systs, const PROconfig &config, cons return it == systs.spline_names.end() ? -1 : (int)std::distance(systs.spline_names.begin(), it); } -// --data + --inject: when the data config carries a , process the data files like MC -// (a PROpeller with per-event truth/model_rule; no systematics) and build the data spectrum -// at the injected physics point. The physics values come from the MC-side fakedataparams, -// matched to the data model by parameter NAME (so the data model may be a different tag or -// order); data-model parameters not given via --inject sit at the data model's default. +// --data + --inject with a data : process data like MC (no systs) and fill at the injected point. static void build_injected_data(std::vector &variable_data, const PROconfig &config, const PROconfig &dataconfig, - const PROmodel &model, const Eigen::VectorXf &fakedataparams, const PROpt &options, - std::uniform_int_distribution &dseed) { - const std::string dataPropBinName = options.analysis_tag + "_data_prop.bin"; + const PROmodel &model, const Eigen::VectorXf &fakedataparams, const PROpt &options, std::uniform_int_distribution &dseed) { + const std::string binName = options.analysis_tag + "_data_prop.bin"; PROpeller dataprop; - if((*options.process_command) || !std::filesystem::exists(dataPropBinName)) { - log(L"%1% || --inject with a data : processing data files into a PROpeller, saving to %2%") % __func__ % dataPropBinName.c_str(); - std::vector> datasysts; // data carries no systematics - PROcess_CAFAna(dataconfig, datasysts, dataprop, options.noxrootd); - dataprop.save(dataPropBinName); + if((*options.process_command) || !std::filesystem::exists(binName)) { + std::vector> nosysts; + PROcess_CAFAna(dataconfig, nosysts, dataprop, options.noxrootd); + dataprop.save(binName); } else { - log(L"%1% || --inject with a data : loading data PROpeller from %2%") % __func__ % dataPropBinName.c_str(); - dataprop.load(dataPropBinName); - if(dataconfig.hash != dataprop.hash) { - if(options.force) { - log(L"%1% || WARNING data config hash (%2%) and binary data PROpeller hash (%3%) not compatible! Forcing ahead.") % __func__ % dataconfig.hash % dataprop.hash; - } else { - log(L"%1% || ERROR data config hash (%2%) and binary data PROpeller hash (%3%) not compatible! Rerun with process.") % __func__ % dataconfig.hash % dataprop.hash; - exit(EXIT_FAILURE); - } + dataprop.load(binName); + if(dataconfig.hash != dataprop.hash && !options.force) { + log(L"%1% || Data config hash (%2%) != %3% hash (%4%). Rerun process or use --force.") % __func__ % dataconfig.hash % binName.c_str() % dataprop.hash; + exit(EXIT_FAILURE); } } - std::unique_ptr datamodel = get_model_from_string(dataconfig, dataprop); - - // Every --inject name must exist in the data model, otherwise the injection would be - // silently dropped from the data. for(const auto &[name, value]: options.fake_data_osc_params) { if(std::find(datamodel->param_names.begin(), datamodel->param_names.end(), name) == datamodel->param_names.end()) { - log(L"%1% || --inject parameter %2% is not a parameter of the data model (tag %3%). Valid names are %4%. Terminating.") - % __func__ % name.c_str() % dataconfig.m_model_tag.c_str() % datamodel->param_names; + log(L"%1% || --inject parameter %2% is not in the data model %3%.") % __func__ % name.c_str() % datamodel->param_names; exit(EXIT_FAILURE); } } - + // Map injected values by name; un-injected data-model params stay at their defaults. Eigen::VectorXf dataparams = datamodel->default_val; for(size_t j = 0; j < datamodel->nparams; ++j) { - const auto it = std::find(model.param_names.begin(), model.param_names.end(), datamodel->param_names[j]); - if(it == model.param_names.end()) { - log(L"%1% || Data model parameter %2% is not in the MC model; using the data model default %3%.") - % __func__ % datamodel->param_names[j].c_str() % dataparams(j); - continue; - } - const size_t k = std::distance(model.param_names.begin(), it); - // fakedataparams is in the MC model's internal (possibly log10) units; convert via - // linear in case the two models disagree on is_log10 for this name. - const float lin = model.is_log10[k] ? std::pow(10.0f, fakedataparams(k)) : fakedataparams(k); + auto it = std::find(model.param_names.begin(), model.param_names.end(), datamodel->param_names[j]); + if(it == model.param_names.end()) continue; + size_t k = std::distance(model.param_names.begin(), it); + float lin = model.is_log10[k] ? std::pow(10.0f, fakedataparams(k)) : fakedataparams(k); dataparams(j) = datamodel->is_log10[j] ? std::log10(lin) : lin; - log(L"%1% || Data injected parameter %2% = %3% (internally %4%)") % __func__ % datamodel->param_names[j].c_str() % lin % dataparams(j); } - + log(L"%1% || Filling data (%2% events) at injected point %3%") % __func__ % dataprop.NEvent() % dataparams.transpose(); PROsyst emptySyst; for(size_t io = 0; io < dataconfig.m_num_variables; ++io) { - const bool fill_this_variable = config.m_channel_variable_plot_bool.at(io) || io == config.i_prime; - PROspec data_spec = fill_this_variable - ? FillSpectra(dataconfig, dataprop, emptySyst, *datamodel, dataparams, !options.eventbyevent, io) - : PROspec(dataconfig.m_num_variable_bins_total[io]); - if(options.poisson_throw) data_spec = PROspec::PoissonVariation(data_spec, dseed(PROseed::global_rng)); - Eigen::VectorXf data_vec = CollapseMatrix(dataconfig, data_spec.Spec(), io); - variable_data.push_back(PROdata(data_vec, data_vec.array().sqrt())); + bool fill = config.m_channel_variable_plot_bool.at(io) || io == config.i_prime; + PROspec spec = fill ? FillSpectra(dataconfig, dataprop, emptySyst, *datamodel, dataparams, !options.eventbyevent, io) + : PROspec(dataconfig.m_num_variable_bins_total[io]); + if(options.poisson_throw) spec = PROspec::PoissonVariation(spec, dseed(PROseed::global_rng)); + Eigen::VectorXf vec = CollapseMatrix(dataconfig, spec.Spec(), io); + variable_data.push_back(PROdata(vec, vec.array().sqrt())); } - log(L"%1% || Built data spectrum with injected physics from %2% data events.") % __func__ % dataprop.NEvent(); } PROdata construct_data(std::vector &variable_data, bool use_real_data, const PROconfig &config, const PROpeller &prop, const PROmodel &model, const std::vector &variable_systs, const Eigen::VectorXf &fakedataparams, const PROpt &options) { @@ -98,7 +73,7 @@ PROdata construct_data(std::vector &variable_data, bool use_real_data, log(L"%1% || Using embedded section from XML for data config") % __func__; dataconfig = config.BuildDataConfig(); } - + std::string dataBinName = options.analysis_tag+"_data.bin"; for(size_t i = 0; i < dataconfig.m_num_channels; ++i) { size_t nsubch = dataconfig.m_num_subchannels[i]; if(nsubch != 1) { @@ -122,14 +97,10 @@ PROdata construct_data(std::vector &variable_data, bool use_real_data, exit(EXIT_FAILURE); } - const bool inject_requested = !options.fake_data_osc_params.empty(); - if(inject_requested && !dataconfig.m_has_model) { - log(L"%1% || WARNING: --inject was given with real data, but the data config has no , so the injection is NOT applied to the data. " - L"Add a to the data XML (or for an embedded section) to enable it.") % __func__; - } - - std::string dataBinName = options.analysis_tag+"_data.bin"; - if(inject_requested && dataconfig.m_has_model) { + const bool inject = !options.fake_data_osc_params.empty(); + if(inject && !dataconfig.m_has_model) + log(L"%1% || --inject is ignored: the data config has no .") % __func__; + if(inject && dataconfig.m_has_model) { build_injected_data(variable_data, config, dataconfig, model, fakedataparams, options, dseed); } else if((*options.process_command) || (!std::filesystem::exists(dataBinName)) ){ log(L"%1% || Processing Data Spectrum and saving to binary output also: %2%") % __func__ % dataBinName.c_str(); diff --git a/inc/PROconfig.h b/inc/PROconfig.h index c2a76200..2583bac9 100644 --- a/inc/PROconfig.h +++ b/inc/PROconfig.h @@ -581,10 +581,7 @@ namespace PROfit{ //Some model infomation std::string m_model_tag; - /// True when a element was parsed from this XML. For a data config - /// (from --data or an embedded section) this enables --inject to - /// reweight the data events at the injected physics point. - bool m_has_model = false; + bool m_has_model = false; ///< A was parsed; lets --inject reweight a data config. std::vector m_model_rule_index; std::vector m_model_rule_names; std::vector m_model_parameter_index; diff --git a/quick.patch b/quick.patch new file mode 100644 index 00000000..c5329183 --- /dev/null +++ b/quick.patch @@ -0,0 +1,201 @@ +diff --git a/bin/PROfit_cli.cxx b/bin/PROfit_cli.cxx +index 1c574b5..0105656 100644 +--- a/bin/PROfit_cli.cxx ++++ b/bin/PROfit_cli.cxx +@@ -15,7 +15,7 @@ PROpt::PROpt(int argc, char **argv) { + app.add_option("--syst-throws", syst_throws, "Number of random throws per spline when converting splines to covariance matrices (spline_to_covariance systematics, and the plot fractional-systematics/covariance breakdowns). Default 500.")->check(CLI::PositiveNumber); + app.add_option("-c, --chi2", chi2, "Which chi2 function to use. Options: neyman (default; stat cov = diag(data)), pearson (stat cov = diag(prediction)), CNP, poisson. Legacy aliases PROchi, PROCNP, Poisson accepted.")->default_str("neyman"); + app.add_option("-d, --data", data_xml, "Load from a seperate data xml/data file instead of signal injection. Only used with plot subcommand.")->default_str(""); +- app.add_option("-i, --inject", fake_data_osc_params, "Physics parameters to inject as fake-data true signal. Example: dmsq 3 sinsq2thmm 0.25. With --data (or an embedded ) whose config has a , the data events are reweighted at this point instead.")->expected(-1); ++ app.add_option("-i, --inject", fake_data_osc_params, "Physics parameters to inject as fake-data true signal. Example: dmsq 3 sinsq2thmm 0.25")->expected(-1); + app.add_option("--inject-cv", cv_osc_params, "Physics parameters to inject as CV. Example: dmsq 3 sinsq2thmm 0.25")->expected(-1); + app.add_option("--fix", fixed_params, "Fix Certain Physics or Systematics parameters. Fixed to CV."); + app.add_option("-s, --seed", global_seed, "A global seed for PROseed rng. Default to -1 for hardware rng seed.")->default_val(-1); +diff --git a/bin/PROfit_setup.cxx b/bin/PROfit_setup.cxx +index 0e34bae..3be4917 100644 +--- a/bin/PROfit_setup.cxx ++++ b/bin/PROfit_setup.cxx +@@ -14,73 +14,48 @@ static int find_spline_index(const PROsyst &systs, const PROconfig &config, cons + return it == systs.spline_names.end() ? -1 : (int)std::distance(systs.spline_names.begin(), it); + } + +-// --data + --inject: when the data config carries a , process the data files like MC +-// (a PROpeller with per-event truth/model_rule; no systematics) and build the data spectrum +-// at the injected physics point. The physics values come from the MC-side fakedataparams, +-// matched to the data model by parameter NAME (so the data model may be a different tag or +-// order); data-model parameters not given via --inject sit at the data model's default. ++// --data + --inject with a data : process data like MC (no systs) and fill at the injected point. + static void build_injected_data(std::vector &variable_data, const PROconfig &config, const PROconfig &dataconfig, +- const PROmodel &model, const Eigen::VectorXf &fakedataparams, const PROpt &options, +- std::uniform_int_distribution &dseed) { +- const std::string dataPropBinName = options.analysis_tag + "_data_prop.bin"; ++ const PROmodel &model, const Eigen::VectorXf &fakedataparams, const PROpt &options, std::uniform_int_distribution &dseed) { ++ const std::string binName = options.analysis_tag + "_data_prop.bin"; + PROpeller dataprop; +- if((*options.process_command) || !std::filesystem::exists(dataPropBinName)) { +- log(L"%1% || --inject with a data : processing data files into a PROpeller, saving to %2%") % __func__ % dataPropBinName.c_str(); +- std::vector> datasysts; // data carries no systematics +- PROcess_CAFAna(dataconfig, datasysts, dataprop, options.noxrootd); +- dataprop.save(dataPropBinName); ++ if((*options.process_command) || !std::filesystem::exists(binName)) { ++ std::vector> nosysts; ++ PROcess_CAFAna(dataconfig, nosysts, dataprop, options.noxrootd); ++ dataprop.save(binName); + } else { +- log(L"%1% || --inject with a data : loading data PROpeller from %2%") % __func__ % dataPropBinName.c_str(); +- dataprop.load(dataPropBinName); +- if(dataconfig.hash != dataprop.hash) { +- if(options.force) { +- log(L"%1% || WARNING data config hash (%2%) and binary data PROpeller hash (%3%) not compatible! Forcing ahead.") % __func__ % dataconfig.hash % dataprop.hash; +- } else { +- log(L"%1% || ERROR data config hash (%2%) and binary data PROpeller hash (%3%) not compatible! Rerun with process.") % __func__ % dataconfig.hash % dataprop.hash; +- exit(EXIT_FAILURE); +- } ++ dataprop.load(binName); ++ if(dataconfig.hash != dataprop.hash && !options.force) { ++ log(L"%1% || Data config hash (%2%) != %3% hash (%4%). Rerun process or use --force.") % __func__ % dataconfig.hash % binName.c_str() % dataprop.hash; ++ exit(EXIT_FAILURE); + } + } +- + std::unique_ptr datamodel = get_model_from_string(dataconfig, dataprop); +- +- // Every --inject name must exist in the data model, otherwise the injection would be +- // silently dropped from the data. + for(const auto &[name, value]: options.fake_data_osc_params) { + if(std::find(datamodel->param_names.begin(), datamodel->param_names.end(), name) == datamodel->param_names.end()) { +- log(L"%1% || --inject parameter %2% is not a parameter of the data model (tag %3%). Valid names are %4%. Terminating.") +- % __func__ % name.c_str() % dataconfig.m_model_tag.c_str() % datamodel->param_names; ++ log(L"%1% || --inject parameter %2% is not in the data model %3%.") % __func__ % name.c_str() % datamodel->param_names; + exit(EXIT_FAILURE); + } + } +- ++ // Map injected values by name; un-injected data-model params stay at their defaults. + Eigen::VectorXf dataparams = datamodel->default_val; + for(size_t j = 0; j < datamodel->nparams; ++j) { +- const auto it = std::find(model.param_names.begin(), model.param_names.end(), datamodel->param_names[j]); +- if(it == model.param_names.end()) { +- log(L"%1% || Data model parameter %2% is not in the MC model; using the data model default %3%.") +- % __func__ % datamodel->param_names[j].c_str() % dataparams(j); +- continue; +- } +- const size_t k = std::distance(model.param_names.begin(), it); +- // fakedataparams is in the MC model's internal (possibly log10) units; convert via +- // linear in case the two models disagree on is_log10 for this name. +- const float lin = model.is_log10[k] ? std::pow(10.0f, fakedataparams(k)) : fakedataparams(k); ++ auto it = std::find(model.param_names.begin(), model.param_names.end(), datamodel->param_names[j]); ++ if(it == model.param_names.end()) continue; ++ size_t k = std::distance(model.param_names.begin(), it); ++ float lin = model.is_log10[k] ? std::pow(10.0f, fakedataparams(k)) : fakedataparams(k); + dataparams(j) = datamodel->is_log10[j] ? std::log10(lin) : lin; +- log(L"%1% || Data injected parameter %2% = %3% (internally %4%)") % __func__ % datamodel->param_names[j].c_str() % lin % dataparams(j); + } +- ++ log(L"%1% || Filling data (%2% events) at injected point %3%") % __func__ % dataprop.NEvent() % dataparams.transpose(); + PROsyst emptySyst; + for(size_t io = 0; io < dataconfig.m_num_variables; ++io) { +- const bool fill_this_variable = config.m_channel_variable_plot_bool.at(io) || io == config.i_prime; +- PROspec data_spec = fill_this_variable +- ? FillSpectra(dataconfig, dataprop, emptySyst, *datamodel, dataparams, !options.eventbyevent, io) +- : PROspec(dataconfig.m_num_variable_bins_total[io]); +- if(options.poisson_throw) data_spec = PROspec::PoissonVariation(data_spec, dseed(PROseed::global_rng)); +- Eigen::VectorXf data_vec = CollapseMatrix(dataconfig, data_spec.Spec(), io); +- variable_data.push_back(PROdata(data_vec, data_vec.array().sqrt())); ++ bool fill = config.m_channel_variable_plot_bool.at(io) || io == config.i_prime; ++ PROspec spec = fill ? FillSpectra(dataconfig, dataprop, emptySyst, *datamodel, dataparams, !options.eventbyevent, io) ++ : PROspec(dataconfig.m_num_variable_bins_total[io]); ++ if(options.poisson_throw) spec = PROspec::PoissonVariation(spec, dseed(PROseed::global_rng)); ++ Eigen::VectorXf vec = CollapseMatrix(dataconfig, spec.Spec(), io); ++ variable_data.push_back(PROdata(vec, vec.array().sqrt())); + } +- log(L"%1% || Built data spectrum with injected physics from %2% data events.") % __func__ % dataprop.NEvent(); + } + + PROdata construct_data(std::vector &variable_data, bool use_real_data, const PROconfig &config, const PROpeller &prop, const PROmodel &model, const std::vector &variable_systs, const Eigen::VectorXf &fakedataparams, const PROpt &options) { +@@ -98,7 +73,7 @@ PROdata construct_data(std::vector &variable_data, bool use_real_data, + log(L"%1% || Using embedded section from XML for data config") % __func__; + dataconfig = config.BuildDataConfig(); + } +- ++ std::string dataBinName = options.analysis_tag+"_data.bin"; + for(size_t i = 0; i < dataconfig.m_num_channels; ++i) { + size_t nsubch = dataconfig.m_num_subchannels[i]; + if(nsubch != 1) { +@@ -122,14 +97,10 @@ PROdata construct_data(std::vector &variable_data, bool use_real_data, + exit(EXIT_FAILURE); + } + +- const bool inject_requested = !options.fake_data_osc_params.empty(); +- if(inject_requested && !dataconfig.m_has_model) { +- log(L"%1% || WARNING: --inject was given with real data, but the data config has no , so the injection is NOT applied to the data. " +- L"Add a to the data XML (or for an embedded section) to enable it.") % __func__; +- } +- +- std::string dataBinName = options.analysis_tag+"_data.bin"; +- if(inject_requested && dataconfig.m_has_model) { ++ const bool inject = !options.fake_data_osc_params.empty(); ++ if(inject && !dataconfig.m_has_model) ++ log(L"%1% || --inject is ignored: the data config has no .") % __func__; ++ if(inject && dataconfig.m_has_model) { + build_injected_data(variable_data, config, dataconfig, model, fakedataparams, options, dseed); + } else if((*options.process_command) || (!std::filesystem::exists(dataBinName)) ){ + log(L"%1% || Processing Data Spectrum and saving to binary output also: %2%") % __func__ % dataBinName.c_str(); +diff --git a/inc/PROconfig.h b/inc/PROconfig.h +index c2a7620..2583bac 100644 +--- a/inc/PROconfig.h ++++ b/inc/PROconfig.h +@@ -581,10 +581,7 @@ namespace PROfit{ + + //Some model infomation + std::string m_model_tag; +- /// True when a element was parsed from this XML. For a data config +- /// (from --data or an embedded section) this enables --inject to +- /// reweight the data events at the injected physics point. +- bool m_has_model = false; ++ bool m_has_model = false; ///< A was parsed; lets --inject reweight a data config. + std::vector m_model_rule_index; + std::vector m_model_rule_names; + std::vector m_model_parameter_index; +diff --git a/src/PROconfig.cxx b/src/PROconfig.cxx +index f4fcce2..402dbfd 100644 +--- a/src/PROconfig.cxx ++++ b/src/PROconfig.cxx +@@ -2442,27 +2442,16 @@ int PROconfig::LoadFromXML(const std::string &filename){ + } + dataXml << "\n"; + +- // Optional model for the data, so --inject can reweight data events (e.g. an MC +- // fake-data sample carrying truth info). This is opt-in and explicit, because real +- // data branches commonly carry a boilerplate model_rule="0": +- // * a element inside is used as-is, or +- // * reuses the top-level . +- // With neither, the data XML is byte-identical to before (no model). +- { +- tinyxml2::XMLElement* pDataModel = pData->FirstChildElement("model"); +- if(!pDataModel && pData->BoolAttribute("inherit_model", false)) { +- pDataModel = doc.FirstChildElement("model"); +- if(!pDataModel) { +- log(L"%1% || but there is no top-level to inherit.") % __func__; +- throw std::invalid_argument(" requires a top-level "); +- } +- log(L"%1% || inherits the top-level (inherit_model=\"true\").") % __func__; +- } +- if(pDataModel) { +- tinyxml2::XMLPrinter printer; +- pDataModel->Accept(&printer); +- dataXml << printer.CStr() << "\n"; +- } ++ // Opt-in data model (for --inject): a inside , or inherit_model="true". ++ tinyxml2::XMLElement* pDataModel = pData->FirstChildElement("model"); ++ if(!pDataModel && pData->BoolAttribute("inherit_model", false)) { ++ pDataModel = doc.FirstChildElement("model"); ++ if(!pDataModel) throw std::invalid_argument(" requires a top-level "); ++ } ++ if(pDataModel) { ++ tinyxml2::XMLPrinter printer; ++ pDataModel->Accept(&printer); ++ dataXml << printer.CStr() << "\n"; + } + + // Serialize MCFile elements from inside the section diff --git a/src/PROconfig.cxx b/src/PROconfig.cxx index f4fcce2c..402dbfdb 100644 --- a/src/PROconfig.cxx +++ b/src/PROconfig.cxx @@ -2442,27 +2442,16 @@ int PROconfig::LoadFromXML(const std::string &filename){ } dataXml << "\n"; - // Optional model for the data, so --inject can reweight data events (e.g. an MC - // fake-data sample carrying truth info). This is opt-in and explicit, because real - // data branches commonly carry a boilerplate model_rule="0": - // * a element inside is used as-is, or - // * reuses the top-level . - // With neither, the data XML is byte-identical to before (no model). - { - tinyxml2::XMLElement* pDataModel = pData->FirstChildElement("model"); - if(!pDataModel && pData->BoolAttribute("inherit_model", false)) { - pDataModel = doc.FirstChildElement("model"); - if(!pDataModel) { - log(L"%1% || but there is no top-level to inherit.") % __func__; - throw std::invalid_argument(" requires a top-level "); - } - log(L"%1% || inherits the top-level (inherit_model=\"true\").") % __func__; - } - if(pDataModel) { - tinyxml2::XMLPrinter printer; - pDataModel->Accept(&printer); - dataXml << printer.CStr() << "\n"; - } + // Opt-in data model (for --inject): a inside , or inherit_model="true". + tinyxml2::XMLElement* pDataModel = pData->FirstChildElement("model"); + if(!pDataModel && pData->BoolAttribute("inherit_model", false)) { + pDataModel = doc.FirstChildElement("model"); + if(!pDataModel) throw std::invalid_argument(" requires a top-level "); + } + if(pDataModel) { + tinyxml2::XMLPrinter printer; + pDataModel->Accept(&printer); + dataXml << printer.CStr() << "\n"; } // Serialize MCFile elements from inside the section From 1defdd46d1495a79d0ff496acd5a6f430c174d9d Mon Sep 17 00:00:00 2001 From: nathanielerowe <70993723+nathanielerowe@users.noreply.github.com> Date: Fri, 16 Oct 2026 12:30:01 -0500 Subject: [PATCH 3/4] rework to minimize changes --- bin/PROfit_setup.cxx | 59 ++++++++++++++++++- inc/PROconfig.h | 1 + quick.patch | 131 +++++++++++++++++++++++++++++++++++++++++++ src/PROconfig.cxx | 13 +++++ 4 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 quick.patch diff --git a/bin/PROfit_setup.cxx b/bin/PROfit_setup.cxx index 164d6d5c..36e02514 100644 --- a/bin/PROfit_setup.cxx +++ b/bin/PROfit_setup.cxx @@ -14,6 +14,52 @@ static int find_spline_index(const PROsyst &systs, const PROconfig &config, cons return it == systs.spline_names.end() ? -1 : (int)std::distance(systs.spline_names.begin(), it); } +// File-name tag for an injected point, e.g. "dmsq3_sinsq2thmm0.25". Values use the short form +// unless it doesn't round-trip, so distinct points never share a cached binary. +static std::string inject_tag(const PROpt &options) { + std::string tag; + for(const auto &[name, value]: options.fake_data_osc_params) { // std::map: sorted by name + std::ostringstream v; v << value; + if(std::stof(v.str()) != value) { v.str(""); v << std::setprecision(std::numeric_limits::max_digits10) << value; } + tag += (tag.empty() ? "" : "_") + name + v.str(); + } + for(char &c: tag) if(!std::isalnum((unsigned char)c) && c != '.' && c != '-' && c != '_') c = '_'; + return tag; +} + +// --data + --inject with a data : process data like MC (no systs) and return the +// histograms at the injected point, ready to be cached like CreatePROdata's output. +static std::vector CreateInjectedPROdata(const PROconfig &dataconfig, const PROmodel &model, + const Eigen::VectorXf &fakedataparams, const PROpt &options) { + PROpeller dataprop; + std::vector> nosysts; + PROcess_CAFAna(dataconfig, nosysts, dataprop, options.noxrootd); + std::unique_ptr datamodel = get_model_from_string(dataconfig, dataprop); + for(const auto &[name, value]: options.fake_data_osc_params) { + if(std::find(datamodel->param_names.begin(), datamodel->param_names.end(), name) == datamodel->param_names.end()) { + log(L"%1% || --inject parameter %2% is not in the data model %3%.") % __func__ % name.c_str() % datamodel->param_names; + exit(EXIT_FAILURE); + } + } + // Map injected values by name; un-injected data-model params stay at their defaults. + Eigen::VectorXf dataparams = datamodel->default_val; + for(size_t j = 0; j < datamodel->nparams; ++j) { + auto it = std::find(model.param_names.begin(), model.param_names.end(), datamodel->param_names[j]); + if(it == model.param_names.end()) continue; + size_t k = std::distance(model.param_names.begin(), it); + float lin = model.is_log10[k] ? std::pow(10.0f, fakedataparams(k)) : fakedataparams(k); + dataparams(j) = datamodel->is_log10[j] ? std::log10(lin) : lin; + } + log(L"%1% || Filling data (%2% events) at injected point %3%") % __func__ % dataprop.NEvent() % dataparams; + PROsyst emptySyst; + std::vector alldata; + for(size_t io = 0; io < dataconfig.m_num_variables; ++io) { + Eigen::VectorXf vec = CollapseMatrix(dataconfig, FillSpectra(dataconfig, dataprop, emptySyst, *datamodel, dataparams, !options.eventbyevent, io).Spec(), io); + alldata.push_back(PROdata(vec, vec.array().sqrt())); + } + return alldata; +} + PROdata construct_data(std::vector &variable_data, bool use_real_data, const PROconfig &config, const PROpeller &prop, const PROmodel &model, const std::vector &variable_systs, const Eigen::VectorXf &fakedataparams, const PROpt &options) { PROdata data; std::uniform_int_distribution dseed(0, std::numeric_limits::max()); @@ -53,11 +99,16 @@ PROdata construct_data(std::vector &variable_data, bool use_real_data, exit(EXIT_FAILURE); } + const bool inject = !options.fake_data_osc_params.empty() && dataconfig.m_has_model; + if(!options.fake_data_osc_params.empty() && !dataconfig.m_has_model) + log(L"%1% || --inject is ignored: the data config has no .") % __func__; + // The injected point is in the name, so a binary is only reused at the same point. + if(inject) dataBinName = options.analysis_tag + "_data_inject_" + inject_tag(options) + ".bin"; if((*options.process_command) || (!std::filesystem::exists(dataBinName)) ){ log(L"%1% || Processing Data Spectrum and saving to binary output also: %2%") % __func__ % dataBinName.c_str(); //Process the CAF files to grab and fill spectrum directly - std::vector alldata = CreatePROdata(dataconfig); + std::vector alldata = inject ? CreateInjectedPROdata(dataconfig, model, fakedataparams, options) : CreatePROdata(dataconfig); PROdata::saveVector(dataconfig, alldata, dataBinName); data = alldata[config.i_prime]; //data.save(dataconfig,dataBinName); @@ -91,6 +142,12 @@ PROdata construct_data(std::vector &variable_data, bool use_real_data, } } + // Cache the Asimov injected spectrum; any Poisson throw is applied fresh each run. + if(inject && options.poisson_throw) + for(auto &d: variable_data) { + PROspec thrown = PROspec::PoissonVariation(PROspec(d.Spec(), d.Error()), dseed(PROseed::global_rng)); + d = PROdata(thrown.Spec(), thrown.Spec().array().sqrt()); + } }//if no data, use injected or fake data; else{ log(L"%1% || Going to get fake data set up for each variable.") % __func__ ; diff --git a/inc/PROconfig.h b/inc/PROconfig.h index 2fd499e8..2583bac9 100644 --- a/inc/PROconfig.h +++ b/inc/PROconfig.h @@ -581,6 +581,7 @@ namespace PROfit{ //Some model infomation std::string m_model_tag; + bool m_has_model = false; ///< A was parsed; lets --inject reweight a data config. std::vector m_model_rule_index; std::vector m_model_rule_names; std::vector m_model_parameter_index; diff --git a/quick.patch b/quick.patch new file mode 100644 index 00000000..85b73525 --- /dev/null +++ b/quick.patch @@ -0,0 +1,131 @@ +diff --git a/bin/PROfit_setup.cxx b/bin/PROfit_setup.cxx +index 164d6d5..36e0251 100644 +--- a/bin/PROfit_setup.cxx ++++ b/bin/PROfit_setup.cxx +@@ -14,6 +14,52 @@ static int find_spline_index(const PROsyst &systs, const PROconfig &config, cons + return it == systs.spline_names.end() ? -1 : (int)std::distance(systs.spline_names.begin(), it); + } + ++// File-name tag for an injected point, e.g. "dmsq3_sinsq2thmm0.25". Values use the short form ++// unless it doesn't round-trip, so distinct points never share a cached binary. ++static std::string inject_tag(const PROpt &options) { ++ std::string tag; ++ for(const auto &[name, value]: options.fake_data_osc_params) { // std::map: sorted by name ++ std::ostringstream v; v << value; ++ if(std::stof(v.str()) != value) { v.str(""); v << std::setprecision(std::numeric_limits::max_digits10) << value; } ++ tag += (tag.empty() ? "" : "_") + name + v.str(); ++ } ++ for(char &c: tag) if(!std::isalnum((unsigned char)c) && c != '.' && c != '-' && c != '_') c = '_'; ++ return tag; ++} ++ ++// --data + --inject with a data : process data like MC (no systs) and return the ++// histograms at the injected point, ready to be cached like CreatePROdata's output. ++static std::vector CreateInjectedPROdata(const PROconfig &dataconfig, const PROmodel &model, ++ const Eigen::VectorXf &fakedataparams, const PROpt &options) { ++ PROpeller dataprop; ++ std::vector> nosysts; ++ PROcess_CAFAna(dataconfig, nosysts, dataprop, options.noxrootd); ++ std::unique_ptr datamodel = get_model_from_string(dataconfig, dataprop); ++ for(const auto &[name, value]: options.fake_data_osc_params) { ++ if(std::find(datamodel->param_names.begin(), datamodel->param_names.end(), name) == datamodel->param_names.end()) { ++ log(L"%1% || --inject parameter %2% is not in the data model %3%.") % __func__ % name.c_str() % datamodel->param_names; ++ exit(EXIT_FAILURE); ++ } ++ } ++ // Map injected values by name; un-injected data-model params stay at their defaults. ++ Eigen::VectorXf dataparams = datamodel->default_val; ++ for(size_t j = 0; j < datamodel->nparams; ++j) { ++ auto it = std::find(model.param_names.begin(), model.param_names.end(), datamodel->param_names[j]); ++ if(it == model.param_names.end()) continue; ++ size_t k = std::distance(model.param_names.begin(), it); ++ float lin = model.is_log10[k] ? std::pow(10.0f, fakedataparams(k)) : fakedataparams(k); ++ dataparams(j) = datamodel->is_log10[j] ? std::log10(lin) : lin; ++ } ++ log(L"%1% || Filling data (%2% events) at injected point %3%") % __func__ % dataprop.NEvent() % dataparams; ++ PROsyst emptySyst; ++ std::vector alldata; ++ for(size_t io = 0; io < dataconfig.m_num_variables; ++io) { ++ Eigen::VectorXf vec = CollapseMatrix(dataconfig, FillSpectra(dataconfig, dataprop, emptySyst, *datamodel, dataparams, !options.eventbyevent, io).Spec(), io); ++ alldata.push_back(PROdata(vec, vec.array().sqrt())); ++ } ++ return alldata; ++} ++ + PROdata construct_data(std::vector &variable_data, bool use_real_data, const PROconfig &config, const PROpeller &prop, const PROmodel &model, const std::vector &variable_systs, const Eigen::VectorXf &fakedataparams, const PROpt &options) { + PROdata data; + std::uniform_int_distribution dseed(0, std::numeric_limits::max()); +@@ -53,11 +99,16 @@ PROdata construct_data(std::vector &variable_data, bool use_real_data, + exit(EXIT_FAILURE); + } + ++ const bool inject = !options.fake_data_osc_params.empty() && dataconfig.m_has_model; ++ if(!options.fake_data_osc_params.empty() && !dataconfig.m_has_model) ++ log(L"%1% || --inject is ignored: the data config has no .") % __func__; ++ // The injected point is in the name, so a binary is only reused at the same point. ++ if(inject) dataBinName = options.analysis_tag + "_data_inject_" + inject_tag(options) + ".bin"; + if((*options.process_command) || (!std::filesystem::exists(dataBinName)) ){ + log(L"%1% || Processing Data Spectrum and saving to binary output also: %2%") % __func__ % dataBinName.c_str(); + + //Process the CAF files to grab and fill spectrum directly +- std::vector alldata = CreatePROdata(dataconfig); ++ std::vector alldata = inject ? CreateInjectedPROdata(dataconfig, model, fakedataparams, options) : CreatePROdata(dataconfig); + PROdata::saveVector(dataconfig, alldata, dataBinName); + data = alldata[config.i_prime]; + //data.save(dataconfig,dataBinName); +@@ -91,6 +142,12 @@ PROdata construct_data(std::vector &variable_data, bool use_real_data, + } + } + ++ // Cache the Asimov injected spectrum; any Poisson throw is applied fresh each run. ++ if(inject && options.poisson_throw) ++ for(auto &d: variable_data) { ++ PROspec thrown = PROspec::PoissonVariation(PROspec(d.Spec(), d.Error()), dseed(PROseed::global_rng)); ++ d = PROdata(thrown.Spec(), thrown.Spec().array().sqrt()); ++ } + }//if no data, use injected or fake data; + else{ + log(L"%1% || Going to get fake data set up for each variable.") % __func__ ; +diff --git a/inc/PROconfig.h b/inc/PROconfig.h +index 2fd499e..2583bac 100644 +--- a/inc/PROconfig.h ++++ b/inc/PROconfig.h +@@ -581,6 +581,7 @@ namespace PROfit{ + + //Some model infomation + std::string m_model_tag; ++ bool m_has_model = false; ///< A was parsed; lets --inject reweight a data config. + std::vector m_model_rule_index; + std::vector m_model_rule_names; + std::vector m_model_parameter_index; +diff --git a/src/PROconfig.cxx b/src/PROconfig.cxx +index ceb2e03..402dbfd 100644 +--- a/src/PROconfig.cxx ++++ b/src/PROconfig.cxx +@@ -2166,6 +2166,7 @@ int PROconfig::LoadFromXML(const std::string &filename){ + pModel = doc.FirstChildElement("model"); + + if(pModel){ ++ m_has_model = true; + // Read in how many bins this channel uses + + const std::vector expected_attrs = {"tag","name","index"}; +@@ -2441,6 +2442,18 @@ int PROconfig::LoadFromXML(const std::string &filename){ + } + dataXml << "\n"; + ++ // Opt-in data model (for --inject): a inside , or inherit_model="true". ++ tinyxml2::XMLElement* pDataModel = pData->FirstChildElement("model"); ++ if(!pDataModel && pData->BoolAttribute("inherit_model", false)) { ++ pDataModel = doc.FirstChildElement("model"); ++ if(!pDataModel) throw std::invalid_argument(" requires a top-level "); ++ } ++ if(pDataModel) { ++ tinyxml2::XMLPrinter printer; ++ pDataModel->Accept(&printer); ++ dataXml << printer.CStr() << "\n"; ++ } ++ + // Serialize MCFile elements from inside the section + tinyxml2::XMLElement* pDataMC = pData->FirstChildElement("MCFile"); + while(pDataMC) { diff --git a/src/PROconfig.cxx b/src/PROconfig.cxx index ceb2e03e..402dbfdb 100644 --- a/src/PROconfig.cxx +++ b/src/PROconfig.cxx @@ -2166,6 +2166,7 @@ int PROconfig::LoadFromXML(const std::string &filename){ pModel = doc.FirstChildElement("model"); if(pModel){ + m_has_model = true; // Read in how many bins this channel uses const std::vector expected_attrs = {"tag","name","index"}; @@ -2441,6 +2442,18 @@ int PROconfig::LoadFromXML(const std::string &filename){ } dataXml << "\n"; + // Opt-in data model (for --inject): a inside , or inherit_model="true". + tinyxml2::XMLElement* pDataModel = pData->FirstChildElement("model"); + if(!pDataModel && pData->BoolAttribute("inherit_model", false)) { + pDataModel = doc.FirstChildElement("model"); + if(!pDataModel) throw std::invalid_argument(" requires a top-level "); + } + if(pDataModel) { + tinyxml2::XMLPrinter printer; + pDataModel->Accept(&printer); + dataXml << printer.CStr() << "\n"; + } + // Serialize MCFile elements from inside the section tinyxml2::XMLElement* pDataMC = pData->FirstChildElement("MCFile"); while(pDataMC) { From 2f4ee90748692aeb0b0bf1fcf38713119514a234 Mon Sep 17 00:00:00 2001 From: nathanielerowe <70993723+nathanielerowe@users.noreply.github.com> Date: Fri, 16 Oct 2026 12:30:15 -0500 Subject: [PATCH 4/4] Remove random file --- quick.patch | 131 ---------------------------------------------------- 1 file changed, 131 deletions(-) delete mode 100644 quick.patch diff --git a/quick.patch b/quick.patch deleted file mode 100644 index 85b73525..00000000 --- a/quick.patch +++ /dev/null @@ -1,131 +0,0 @@ -diff --git a/bin/PROfit_setup.cxx b/bin/PROfit_setup.cxx -index 164d6d5..36e0251 100644 ---- a/bin/PROfit_setup.cxx -+++ b/bin/PROfit_setup.cxx -@@ -14,6 +14,52 @@ static int find_spline_index(const PROsyst &systs, const PROconfig &config, cons - return it == systs.spline_names.end() ? -1 : (int)std::distance(systs.spline_names.begin(), it); - } - -+// File-name tag for an injected point, e.g. "dmsq3_sinsq2thmm0.25". Values use the short form -+// unless it doesn't round-trip, so distinct points never share a cached binary. -+static std::string inject_tag(const PROpt &options) { -+ std::string tag; -+ for(const auto &[name, value]: options.fake_data_osc_params) { // std::map: sorted by name -+ std::ostringstream v; v << value; -+ if(std::stof(v.str()) != value) { v.str(""); v << std::setprecision(std::numeric_limits::max_digits10) << value; } -+ tag += (tag.empty() ? "" : "_") + name + v.str(); -+ } -+ for(char &c: tag) if(!std::isalnum((unsigned char)c) && c != '.' && c != '-' && c != '_') c = '_'; -+ return tag; -+} -+ -+// --data + --inject with a data : process data like MC (no systs) and return the -+// histograms at the injected point, ready to be cached like CreatePROdata's output. -+static std::vector CreateInjectedPROdata(const PROconfig &dataconfig, const PROmodel &model, -+ const Eigen::VectorXf &fakedataparams, const PROpt &options) { -+ PROpeller dataprop; -+ std::vector> nosysts; -+ PROcess_CAFAna(dataconfig, nosysts, dataprop, options.noxrootd); -+ std::unique_ptr datamodel = get_model_from_string(dataconfig, dataprop); -+ for(const auto &[name, value]: options.fake_data_osc_params) { -+ if(std::find(datamodel->param_names.begin(), datamodel->param_names.end(), name) == datamodel->param_names.end()) { -+ log(L"%1% || --inject parameter %2% is not in the data model %3%.") % __func__ % name.c_str() % datamodel->param_names; -+ exit(EXIT_FAILURE); -+ } -+ } -+ // Map injected values by name; un-injected data-model params stay at their defaults. -+ Eigen::VectorXf dataparams = datamodel->default_val; -+ for(size_t j = 0; j < datamodel->nparams; ++j) { -+ auto it = std::find(model.param_names.begin(), model.param_names.end(), datamodel->param_names[j]); -+ if(it == model.param_names.end()) continue; -+ size_t k = std::distance(model.param_names.begin(), it); -+ float lin = model.is_log10[k] ? std::pow(10.0f, fakedataparams(k)) : fakedataparams(k); -+ dataparams(j) = datamodel->is_log10[j] ? std::log10(lin) : lin; -+ } -+ log(L"%1% || Filling data (%2% events) at injected point %3%") % __func__ % dataprop.NEvent() % dataparams; -+ PROsyst emptySyst; -+ std::vector alldata; -+ for(size_t io = 0; io < dataconfig.m_num_variables; ++io) { -+ Eigen::VectorXf vec = CollapseMatrix(dataconfig, FillSpectra(dataconfig, dataprop, emptySyst, *datamodel, dataparams, !options.eventbyevent, io).Spec(), io); -+ alldata.push_back(PROdata(vec, vec.array().sqrt())); -+ } -+ return alldata; -+} -+ - PROdata construct_data(std::vector &variable_data, bool use_real_data, const PROconfig &config, const PROpeller &prop, const PROmodel &model, const std::vector &variable_systs, const Eigen::VectorXf &fakedataparams, const PROpt &options) { - PROdata data; - std::uniform_int_distribution dseed(0, std::numeric_limits::max()); -@@ -53,11 +99,16 @@ PROdata construct_data(std::vector &variable_data, bool use_real_data, - exit(EXIT_FAILURE); - } - -+ const bool inject = !options.fake_data_osc_params.empty() && dataconfig.m_has_model; -+ if(!options.fake_data_osc_params.empty() && !dataconfig.m_has_model) -+ log(L"%1% || --inject is ignored: the data config has no .") % __func__; -+ // The injected point is in the name, so a binary is only reused at the same point. -+ if(inject) dataBinName = options.analysis_tag + "_data_inject_" + inject_tag(options) + ".bin"; - if((*options.process_command) || (!std::filesystem::exists(dataBinName)) ){ - log(L"%1% || Processing Data Spectrum and saving to binary output also: %2%") % __func__ % dataBinName.c_str(); - - //Process the CAF files to grab and fill spectrum directly -- std::vector alldata = CreatePROdata(dataconfig); -+ std::vector alldata = inject ? CreateInjectedPROdata(dataconfig, model, fakedataparams, options) : CreatePROdata(dataconfig); - PROdata::saveVector(dataconfig, alldata, dataBinName); - data = alldata[config.i_prime]; - //data.save(dataconfig,dataBinName); -@@ -91,6 +142,12 @@ PROdata construct_data(std::vector &variable_data, bool use_real_data, - } - } - -+ // Cache the Asimov injected spectrum; any Poisson throw is applied fresh each run. -+ if(inject && options.poisson_throw) -+ for(auto &d: variable_data) { -+ PROspec thrown = PROspec::PoissonVariation(PROspec(d.Spec(), d.Error()), dseed(PROseed::global_rng)); -+ d = PROdata(thrown.Spec(), thrown.Spec().array().sqrt()); -+ } - }//if no data, use injected or fake data; - else{ - log(L"%1% || Going to get fake data set up for each variable.") % __func__ ; -diff --git a/inc/PROconfig.h b/inc/PROconfig.h -index 2fd499e..2583bac 100644 ---- a/inc/PROconfig.h -+++ b/inc/PROconfig.h -@@ -581,6 +581,7 @@ namespace PROfit{ - - //Some model infomation - std::string m_model_tag; -+ bool m_has_model = false; ///< A was parsed; lets --inject reweight a data config. - std::vector m_model_rule_index; - std::vector m_model_rule_names; - std::vector m_model_parameter_index; -diff --git a/src/PROconfig.cxx b/src/PROconfig.cxx -index ceb2e03..402dbfd 100644 ---- a/src/PROconfig.cxx -+++ b/src/PROconfig.cxx -@@ -2166,6 +2166,7 @@ int PROconfig::LoadFromXML(const std::string &filename){ - pModel = doc.FirstChildElement("model"); - - if(pModel){ -+ m_has_model = true; - // Read in how many bins this channel uses - - const std::vector expected_attrs = {"tag","name","index"}; -@@ -2441,6 +2442,18 @@ int PROconfig::LoadFromXML(const std::string &filename){ - } - dataXml << "\n"; - -+ // Opt-in data model (for --inject): a inside , or inherit_model="true". -+ tinyxml2::XMLElement* pDataModel = pData->FirstChildElement("model"); -+ if(!pDataModel && pData->BoolAttribute("inherit_model", false)) { -+ pDataModel = doc.FirstChildElement("model"); -+ if(!pDataModel) throw std::invalid_argument(" requires a top-level "); -+ } -+ if(pDataModel) { -+ tinyxml2::XMLPrinter printer; -+ pDataModel->Accept(&printer); -+ dataXml << printer.CStr() << "\n"; -+ } -+ - // Serialize MCFile elements from inside the section - tinyxml2::XMLElement* pDataMC = pData->FirstChildElement("MCFile"); - while(pDataMC) {