From 61749b91c4fb59bf22c7e4ec9f777adf9d90d878 Mon Sep 17 00:00:00 2001 From: "Ryan M. L. McFadden" Date: Mon, 17 Jun 2024 10:51:44 -0300 Subject: [PATCH 1/3] fix segfault This patch provides a memory-safe alternative to the changes introduced in commit 418adfde670b8d911a6a313e11a2ee4fc98feeee, which causes a segfault when the "batch mode" flag is required (i.e., for ascii/graphic export). Note: the program name (i.e., argv[0]) has been added the list arguments passed to ROOT's TApplication. This ensures that the TApplication name matches that of the program (see https://github.com/root-project/root/blob/542b98b2ccca760fd83117b750b89d81b8e9b926/core/base/src/TApplication.cxx#L179-L180). --- src/musrFT.cpp | 15 +++++++-------- src/musrview.cpp | 15 ++++++++------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/musrFT.cpp b/src/musrFT.cpp index a2d50364..a9bf6ca1 100644 --- a/src/musrFT.cpp +++ b/src/musrFT.cpp @@ -1437,19 +1437,18 @@ Int_t main(Int_t argc, Char_t *argv[]) musrFT_dumpData(startupParam.dumpFln, fourier, startupParam.fourierRange[0], startupParam.fourierRange[1]); } else { // do Canvas - // if Fourier graphical export is whished, switch to batch mode + // if Fourier graphical export is wished, switch to batch mode Bool_t batch = false; - int cc=0; - char **arg; + // create list of essential arguments to pass to the ROOT application + std::vector args; + args.push_back(argv[0]); // program name if (startupParam.graphicFormat.Length() != 0) { batch = true; - arg[cc] = (Char_t*)malloc(16*sizeof(Char_t)); - strcpy(arg[cc], "-b"); - cc++; + args.push_back((char*)"-b"); // batch mode flag } - + int cc = args.size(); // plot the Fourier transform - TApplication app("App", &cc, arg); + TApplication app("App", &cc, args.data()); if (startupHandler) { fourierCanvas = std::unique_ptr(new PFourierCanvas(fourier, dataSetTag, startupParam.title.Data(), diff --git a/src/musrview.cpp b/src/musrview.cpp index b76383e0..69ba93d7 100644 --- a/src/musrview.cpp +++ b/src/musrview.cpp @@ -37,6 +37,7 @@ #include #include +#include #include #include @@ -301,15 +302,15 @@ int main(int argc, char *argv[]) } if (result == PMUSR_SUCCESS) { - // generate Root application needed for PMusrCanvas - int cc=0; - char **arg; + // create the ROOT application needed for PMusrCanvas + // and pass it only essential arguments + std::vector args; + args.push_back(argv[0]); // program name if (graphicsOutput || asciiOutput) { - arg[cc] = (char*)malloc(16*sizeof(char)); - strcpy(arg[cc], "-b"); - cc++; + args.push_back((char*)"-b"); // batch mode flag } - TApplication app("App", &cc, arg); + int cc = args.size(); + TApplication app("App", &cc, args.data()); std::vector canvasVector; PMusrCanvas *musrCanvas; From 833171c71204d39089ed50b8012619af357412ab Mon Sep 17 00:00:00 2001 From: "Ryan M. L. McFadden" Date: Mon, 17 Jun 2024 12:27:33 -0300 Subject: [PATCH 2/3] write the fit results to an easy-to-read/parse yaml file This patch adds routines for writing a comprehensive set of fit results (i.e., parameter values, parabolic errors, asymmetric errors, covariances, correlation coefficients, etc.) for an individual `.msr` file to an easy-to-read/parse `.yaml` file. The main motivation for the code addition is to provide users with easy access to the fit's covariance matrix without the need for "extra" manual effort (e.g., parsing the contents of `MINUIT2.OUTPUT` or `MINUIT2.root`). The other fit quantities are also included for completeness. Reading/accessing the output is trivial using, for example, the PyYAML Python library (https://github.com/yaml/pyyaml): ```python import yaml with open("2125_tf_histo.yaml", "r") as fh: results = yaml.load(fh, Loader=yaml.SafeLoader) cov = results["covariance"]["Field_1"]["Sigma_1"] ``` Note: the naming conventions chosen for the blocks in the `.yaml` output closely follow those used by the iminuit Python library (https://github.com/scikit-hep/iminuit). --- src/classes/PFitter.cpp | 118 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 117 insertions(+), 1 deletion(-) diff --git a/src/classes/PFitter.cpp b/src/classes/PFitter.cpp index 41d2ac26..ac1c0a3f 100644 --- a/src/classes/PFitter.cpp +++ b/src/classes/PFitter.cpp @@ -39,9 +39,10 @@ #include #include #include - #include +#include + #include #include "Minuit2/FunctionMinimum.h" @@ -2473,6 +2474,121 @@ Bool_t PFitter::ExecuteSave(Bool_t firstSave) ccorr->Write("ccorr", TObject::kOverwrite, sizeof(ccorr)); hcorr->Write("hcorr", TObject::kOverwrite, sizeof(hcorr)); ff.Close(); + + // write the fit results to an easy-to-read/parse yaml file + // note: the block names follow those used by Python library iminuit + // https://github.com/scikit-hep/iminuit + + // dynamically name the yaml output file + // https://stackoverflow.com/a/25389052 + std::string yaml_filename(fRunInfo->GetFileName().Data()); + const std::string msr_ext(".msr"); + yaml_filename.replace(yaml_filename.find(msr_ext), msr_ext.length(), + ".yaml"); + + // define yaml's 2-space indentation + const std::string yaml_indent(" "); + + // open the yaml file for writing + std::ofstream yaml_file(yaml_filename); + + // number formatting of the output + yaml_file << std::scientific << std::setprecision(16); + + // write the parameter values + yaml_file << "values:\n"; + for (unsigned int i = 0; i < fParams.size(); ++i) { + yaml_file << yaml_indent << fParams[i].fName.Data() << ": " + << fParams[i].fValue << "\n"; + } + + // write the parabolic errors + yaml_file << "errors:\n"; + for (unsigned int i = 0; i < fParams.size(); ++i) { + yaml_file << yaml_indent << fParams[i].fName.Data() << ": " + << fMnUserParams.Error(i) << "\n"; + } + + // write the minos errors + yaml_file << "mnerrors:\n"; + for (unsigned int i = 0; i < fParams.size(); ++i) { + // use boost's implementation of a variant, which can be streamed by + // default - see: https://stackoverflow.com/q/47168477 + boost::variant positive_error, negative_error; + if (fParams[i].fPosErrorPresent) { + positive_error = fParams[i].fPosError; + negative_error = fParams[i].fStep; + } else { + positive_error = "null"; + negative_error = "null"; + } + + yaml_file << yaml_indent << fParams[i].fName.Data() << ":\n"; + yaml_file << yaml_indent << yaml_indent + << "positive: " << positive_error << "\n"; + yaml_file << yaml_indent << yaml_indent + << "negative: " << negative_error << "\n"; + } + + // write the parameter limits + yaml_file << "limits:\n"; + for (unsigned int i = 0; i < fParams.size(); ++i) { + // use boost's implementation of a variant, which can be streamed by + // default - see: https://stackoverflow.com/q/47168477 + boost::variant upper_limit, lower_limit; + if (fParams[i].fLowerBoundaryPresent) { + lower_limit = fParams[i].fLowerBoundary; + } else { + lower_limit = "null"; + } + if (fParams[i].fUpperBoundaryPresent) { + upper_limit = fParams[i].fUpperBoundary; + } else { + upper_limit = "null"; + } + + yaml_file << yaml_indent << fParams[i].fName.Data() << ":\n"; + yaml_file << yaml_indent << yaml_indent << "lower: " << lower_limit + << "\n"; + yaml_file << yaml_indent << yaml_indent << "upper: " << upper_limit + << "\n"; + } + + // write if the parameter is fixed + yaml_file << "fixed:\n"; + for (unsigned int i = 0; i < fParams.size(); ++i) { + std::string is_fixed = fParams[i].fStep == 0.0 ? "true" : "false"; + yaml_file << yaml_indent << fParams[i].fName.Data() << ": " << is_fixed + << "\n"; + } + + // write the covariance matrix (omitting fixed parameters) + yaml_file << "covariance:\n"; + for (unsigned int i = 0; i < cov.Nrow(); ++i) { + yaml_file << yaml_indent << mnState.Name(parNo[i]) << ":\n"; + for (unsigned int j = 0; j < cov.Nrow(); ++j) { + yaml_file << yaml_indent << yaml_indent << mnState.Name(parNo[j]) + << ": " << cov(i, j) << "\n"; + } + } + + // write the correlation matrix (omitting fixed parameters) + yaml_file << "correlation:\n"; + for (unsigned int i = 0; i < cov.Nrow(); ++i) { + yaml_file << yaml_indent << mnState.Name(parNo[i]) << ":\n"; + for (unsigned int j = 0; j < cov.Nrow(); ++j) { + double correlation = + i == j ? 1.0 + : cov(i, j) / (fMnUserParams.Error(parNo[i]) * + fMnUserParams.Error(parNo[j])); + yaml_file << yaml_indent << yaml_indent << mnState.Name(parNo[j]) + << ": " << correlation << "\n"; + } + } + + // close the yaml file + yaml_file.close(); + } parNo.clear(); // clean up } else { From 8821112927c589795f8131d576f81d16b87683f2 Mon Sep 17 00:00:00 2001 From: "Ryan M. L. McFadden" Date: Mon, 17 Jun 2024 13:06:40 -0300 Subject: [PATCH 3/3] ignore all files generated from an in-repo build --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..8d9b7daa --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# ignore all files generated from an in-repo build +build/