From cd1a7b3026b9e0c26d0cac1613c88fb52a3c303b Mon Sep 17 00:00:00 2001 From: Ewan Date: Wed, 9 Oct 2024 06:26:56 -0700 Subject: [PATCH 01/24] better structure for python binding code --- CMakeLists.txt | 19 +++---------------- python/CMakeLists.txt | 16 ++++++++++++++++ .../plotting.cpp | 8 ++++---- pyMaCh3.cpp => python/pyMaCh3.cpp | 2 +- 4 files changed, 24 insertions(+), 21 deletions(-) create mode 100644 python/CMakeLists.txt rename plotting/plottingUtils/pythonPlottingModule.cpp => python/plotting.cpp (97%) rename pyMaCh3.cpp => python/pyMaCh3.cpp (58%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 02bb5c249..982043aba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -213,25 +213,12 @@ add_subdirectory(samplePDF) add_subdirectory(mcmc) add_subdirectory(Diagnostics) add_subdirectory(plotting) - - -################################# pybind11 stuff ################################## - -if( MaCh3_PYTHON_ENABLED ) - ## EM: make a module target out of all the python*Module.cpp files (currently just one...) - pybind11_add_module( - pyMaCh3 MODULE - pyMaCh3.cpp - plotting/plottingUtils/pythonPlottingModule.cpp - ) - ## EM: only works with code compiled with -fPIC enabled.. I think this flag can things slightly slower - ## so would be good to find a way around this. - set_property( TARGET pyMaCh3 PROPERTY POSITION_INDEPENDENT_CODE ON ) - target_link_libraries( pyMaCh3 PUBLIC Plotting ) - install( TARGETS pyMaCh3 DESTINATION pyMaCh3/) +if (MaCh3_PYTHON_ENABLED) + add_subdirectory(python) endif() + #This is to export the target properties of MaCh3 #Anything that links to "MaCh3" will get all of these target properties add_library(MaCh3 INTERFACE) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt new file mode 100644 index 000000000..3063d01c1 --- /dev/null +++ b/python/CMakeLists.txt @@ -0,0 +1,16 @@ + +################################# pybind11 stuff ################################## + +if( MaCh3_PYTHON_ENABLED ) + ## EM: make a module target out of all the python*Module.cpp files (currently just one...) + pybind11_add_module( + pyMaCh3 MODULE + pyMaCh3.cpp + plotting.cpp + ) + ## EM: only works with code compiled with -fPIC enabled.. I think this flag can make things slightly slower + ## so would be good to find a way around this. + set_property( TARGET pyMaCh3 PROPERTY POSITION_INDEPENDENT_CODE ON ) + target_link_libraries( pyMaCh3 PUBLIC Plotting ) + install( TARGETS pyMaCh3 DESTINATION pyMaCh3/) +endif() diff --git a/plotting/plottingUtils/pythonPlottingModule.cpp b/python/plotting.cpp similarity index 97% rename from plotting/plottingUtils/pythonPlottingModule.cpp rename to python/plotting.cpp index 708f579bb..765c7b0bf 100644 --- a/plotting/plottingUtils/pythonPlottingModule.cpp +++ b/python/plotting.cpp @@ -4,10 +4,10 @@ #include #include -#include "plottingUtils.h" -#include "plottingManager.h" -#include "styleManager.h" -#include "inputManager.h" +#include "plotting/plottingUtils/plottingUtils.h" +#include "plotting/plottingUtils/plottingManager.h" +#include "plotting/plottingUtils/styleManager.h" +#include "plotting/plottingUtils/inputManager.h" namespace py = pybind11; diff --git a/pyMaCh3.cpp b/python/pyMaCh3.cpp similarity index 58% rename from pyMaCh3.cpp rename to python/pyMaCh3.cpp index 189c8174b..c188b39ab 100644 --- a/pyMaCh3.cpp +++ b/python/pyMaCh3.cpp @@ -3,7 +3,7 @@ namespace py = pybind11; -void initPlotting(py::module &); // <- defined in plotting/plottingUtils/pythonPlottingModule.cpp +void initPlotting(py::module &); // <- defined in plotting.cpp PYBIND11_MODULE( pyMaCh3, m ) { initPlotting(m); From 7b3b09b3bd3517306ada3eb5b94cbe6c01420646 Mon Sep 17 00:00:00 2001 From: Ewan Date: Wed, 9 Oct 2024 09:13:18 -0700 Subject: [PATCH 02/24] add python wrapping for minimal set of things to get FitterBase working in python --- python/CMakeLists.txt | 5 +- python/fitter.cpp | 108 ++++++++++++++++++++++++++++++++++++++++++ python/manager.cpp | 31 ++++++++++++ python/pyMaCh3.cpp | 8 +++- python/samplePDF.cpp | 92 +++++++++++++++++++++++++++++++++++ 5 files changed, 242 insertions(+), 2 deletions(-) create mode 100644 python/fitter.cpp create mode 100644 python/manager.cpp create mode 100644 python/samplePDF.cpp diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 3063d01c1..2b327fcc9 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -7,10 +7,13 @@ if( MaCh3_PYTHON_ENABLED ) pyMaCh3 MODULE pyMaCh3.cpp plotting.cpp + fitter.cpp + samplePDF.cpp + manager.cpp ) ## EM: only works with code compiled with -fPIC enabled.. I think this flag can make things slightly slower ## so would be good to find a way around this. set_property( TARGET pyMaCh3 PROPERTY POSITION_INDEPENDENT_CODE ON ) - target_link_libraries( pyMaCh3 PUBLIC Plotting ) + target_link_libraries( pyMaCh3 PUBLIC MaCh3::All ) install( TARGETS pyMaCh3 DESTINATION pyMaCh3/) endif() diff --git a/python/fitter.cpp b/python/fitter.cpp new file mode 100644 index 000000000..2cba9ffa3 --- /dev/null +++ b/python/fitter.cpp @@ -0,0 +1,108 @@ +#include +#include + +#include "mcmc/FitterBase.h" + +namespace py = pybind11; + +// As FitterBase is an abstract base class we have to do some gymnastics to get it to get it into python +class PyFitterBase : public FitterBase { +public: + /* Inherit the constructors */ + using FitterBase::FitterBase; + + /* Trampoline (need one for each virtual function) */ + void runMCMC() override { + PYBIND11_OVERRIDE_PURE( + void, /* Return type */ + FitterBase, /* Parent class */ + runMCMC, /* Name of function in C++ (must match Python name) */ + /* Argument(s) */ + ); + } + + std::string GetName() const override { + PYBIND11_OVERRIDE_PURE( + std::string, /* Return type */ + FitterBase, /* Parent class */ + GetName, /* Name of function in C++ (must match Python name) */ + /* Argument(s) */ + ); + } +}; + +void initFitter(py::module &m){ + + auto m_fitter = m.def_submodule("fitter"); + m_fitter.doc() = + "This module contains the various MaCh3 fitter algorithms which are available, \ + as well as the :py:class:`pyMaCh3.fitter.FitterBase` class which you can use to \ + implement your own!"; + + + py::class_(m_fitter, "FitterBase") + .def(py::init()) + + .def( + "runMCMC", + &FitterBase::runMCMC, + "The implementation of the fitter, you should override this with your own desired fitting algorithm" + ) + + .def( + "GetName", + &FitterBase::GetName, + " The name of the algorithm, you should override this with something like \n" + "''' \n" + "return 'mySuperCoolAlgoName' \n" + "''' \n" + ) + + .def( + "run_LLH_scan", + &FitterBase::RunLLHScan, + "Perform a 1D likelihood scan" + ) + + .def( + "get_step_scale_from_LLH_scan", + &FitterBase::GetStepScaleBasedOnLLHScan, + "LLH scan is good first estimate of step scale, this will get the rough estimates for the step scales based on running an LLH scan" + ) + + .def( + "run_2d_LLH_scan", + &FitterBase::Run2DLLHScan, + " Perform a 2D likelihood scan. \n" + " *warning* This operation may take a significant amount of time, especially for complex models." + ) + + .def( + "run_sigma_var", + &FitterBase::RunSigmaVar, + " Perform a 2D and 1D sigma var for all samples. \n" + " *warning* Code uses TH2Poly" + ) + + .def( + "drag_race", + &FitterBase::DragRace, + " Calculates the required time for each sample or covariance object in a drag race simulation. Inspired by Dan's feature \n" + " *NLaps* number of laps, every part of Fitter will be tested with given number of laps and you will get total and average time", + py::arg("NLaps") = 100 + ) + + // stuff for registering other objects with the fitter + + .def( + "add_sample_PDF", + &FitterBase::addSamplePDF, + " This function adds a sample PDF object to the analysis framework. The sample PDF object will be utilized in fitting procedures or likelihood scans. \n" + " *sample* A sample PDF object derived from samplePDFBase. " + ) + + ; + + + +} \ No newline at end of file diff --git a/python/manager.cpp b/python/manager.cpp new file mode 100644 index 000000000..f2d41b4b6 --- /dev/null +++ b/python/manager.cpp @@ -0,0 +1,31 @@ +#include +#include + +#include "manager/manager.h" + +namespace py = pybind11; + +void initManager(py::module &m){ + + auto m_manager = m.def_submodule("manager"); + m_manager.doc() = + "This module really just exists to hold the :py:class:`pyMaCh3.manager.Manager` class. The reason for this slightly weird structure is to mimic the structure of the c++ version of Mach3. \ + You can read more about the manager and config files on [the wiki page](https://github.com/mach3-software/MaCh3/wiki/01.-Manager-and-config-handling). \ + Happy managing!"; + + + py::class_(m_manager, "Manager") + .def( + py::init(), + "create a Manager object with a specified *output_name", + py::arg("output_name") + ) + + .def( + "print", + &manager::Print, + "Print currently used config." + ) + ; + +}; diff --git a/python/pyMaCh3.cpp b/python/pyMaCh3.cpp index c188b39ab..6107c37b3 100644 --- a/python/pyMaCh3.cpp +++ b/python/pyMaCh3.cpp @@ -3,8 +3,14 @@ namespace py = pybind11; -void initPlotting(py::module &); // <- defined in plotting.cpp +void initPlotting(py::module &); // <- defined in python/plotting.cpp +void initFitter(py::module &); // <- defined in python/fitter.cpp +void initSamplePDF(py::module &); // <- defined in python/samplePDF.cpp +void initManager(py::module &); // <- defined in python/manager.cpp PYBIND11_MODULE( pyMaCh3, m ) { initPlotting(m); + initFitter(m); + initSamplePDF(m); + initManager(m); } \ No newline at end of file diff --git a/python/samplePDF.cpp b/python/samplePDF.cpp new file mode 100644 index 000000000..be4536503 --- /dev/null +++ b/python/samplePDF.cpp @@ -0,0 +1,92 @@ +#include +#include + +#include "samplePDF/samplePDFBase.h" +#include "samplePDF/samplePDFFDBase.h" + +namespace py = pybind11; + +// As SamplePDFBase is an abstract base class we have to do some gymnastics to get it to get it into python +class PySamplePDFBase : public samplePDFBase { +public: + /* Inherit the constructors */ + using samplePDFBase::samplePDFBase; + + /* Trampoline (need one for each virtual function) */ + void reweight() override { + PYBIND11_OVERRIDE_PURE( + void, /* Return type */ + samplePDFBase, /* Parent class */ + reweight, /* Name of function in C++ (must match Python name) */ + /* Argument(s) */ + ); + } + + double GetLikelihood() override { + PYBIND11_OVERRIDE_PURE( + double, /* Return type */ + samplePDFBase, /* Parent class */ + GetLikelihood, /* Name of function in C++ (must match Python name) */ + /* Argument(s) */ + ); + } + + void fill1DHist() override { + PYBIND11_OVERRIDE_PURE( + void, /* Return type */ + samplePDFBase, /* Parent class */ + fill1DHist, /* Name of function in C++ (must match Python name) */ + /* Argument(s) */ + ); + } + + void fill2DHist() override { + PYBIND11_OVERRIDE_PURE( + void, /* Return type */ + samplePDFBase, /* Parent class */ + fill2DHist /* Name of function in C++ (must match Python name) */ + /* Argument(s) */ + ); + } +}; + +void initSamplePDF(py::module &m){ + + auto m_sample_pdf = m.def_submodule("sample_pdf"); + m_sample_pdf.doc() = + "This module deals with sampling from the posterior density function of your particular experimental model at different points, given your data. \ + In order to do this, you will generally need to create a SamplePDF object derived from :py:class:`pyMaCh3.fitter.SamplePDFBase` for each sample of events for your experiment. \ + For some more details on this you can see [the wiki page](https://github.com/mach3-software/MaCh3/wiki/04.-Making-a-samplePDF-experiment-class) on this. The code examples there are written using c++ however the general ideas are the same. \ + Happy sampling!"; + + + py::class_(m_sample_pdf, "SamplePDFBase") + .def(py::init()) + + .def( + "reweight", + &samplePDFBase::reweight, + "reweight the MC events in this sample. You will need to override this." + ) + + .def( + "get_likelihood", + &samplePDFBase::GetLikelihood, + "Get the sample likelihood at the current point in your model space. You will need to override this." + ) + + .def( + "fill_1d_hist", + &samplePDFBase::fill1DHist, + "Do the initial filling of the sample for 1d histogram. You will need to override this." + ) + + .def( + "fill_2d_hist", + &samplePDFBase::fill2DHist, + "Do the initial filling of the sample for 2d histogram. You will need to override this." + ) + + ; + +} \ No newline at end of file From 68f406bd067130862062c6beca9c7624ab1c358f Mon Sep 17 00:00:00 2001 From: Ewan Date: Wed, 9 Oct 2024 09:36:02 -0700 Subject: [PATCH 03/24] sphinx docs for new python stuff --- Doc/sphinx/source/fitter.rst | 7 +++++++ Doc/sphinx/source/manager.rst | 7 +++++++ Doc/sphinx/source/pyMaCh3.rst | 3 +++ Doc/sphinx/source/sample-pdf.rst | 7 +++++++ python/fitter.cpp | 7 ++++--- python/manager.cpp | 4 ++-- 6 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 Doc/sphinx/source/fitter.rst create mode 100644 Doc/sphinx/source/manager.rst create mode 100644 Doc/sphinx/source/sample-pdf.rst diff --git a/Doc/sphinx/source/fitter.rst b/Doc/sphinx/source/fitter.rst new file mode 100644 index 000000000..8e40a06cb --- /dev/null +++ b/Doc/sphinx/source/fitter.rst @@ -0,0 +1,7 @@ +Fitter +======== + +.. automodapi:: pyMaCh3.fitter + :members: + :undoc-members: + :show-inheritance: diff --git a/Doc/sphinx/source/manager.rst b/Doc/sphinx/source/manager.rst new file mode 100644 index 000000000..d04dbd315 --- /dev/null +++ b/Doc/sphinx/source/manager.rst @@ -0,0 +1,7 @@ +Manager +======= + +.. automodapi:: pyMaCh3.manager + :members: + :undoc-members: + :show-inheritance: diff --git a/Doc/sphinx/source/pyMaCh3.rst b/Doc/sphinx/source/pyMaCh3.rst index 580ac0dae..750ee0e22 100644 --- a/Doc/sphinx/source/pyMaCh3.rst +++ b/Doc/sphinx/source/pyMaCh3.rst @@ -4,4 +4,7 @@ pyMaCh3 .. toctree:: :maxdepth: 4 + manager.rst + sample-pdf.rst + fitter.rst plotting.rst \ No newline at end of file diff --git a/Doc/sphinx/source/sample-pdf.rst b/Doc/sphinx/source/sample-pdf.rst new file mode 100644 index 000000000..84451b779 --- /dev/null +++ b/Doc/sphinx/source/sample-pdf.rst @@ -0,0 +1,7 @@ +Sample PDF +========== + +.. automodapi:: pyMaCh3.sample_pdf + :members: + :undoc-members: + :show-inheritance: diff --git a/python/fitter.cpp b/python/fitter.cpp index 2cba9ffa3..fcb837c69 100644 --- a/python/fitter.cpp +++ b/python/fitter.cpp @@ -53,9 +53,9 @@ void initFitter(py::module &m){ "GetName", &FitterBase::GetName, " The name of the algorithm, you should override this with something like \n" - "''' \n" + "``` \n" "return 'mySuperCoolAlgoName' \n" - "''' \n" + "``` \n" ) .def( @@ -98,7 +98,8 @@ void initFitter(py::module &m){ "add_sample_PDF", &FitterBase::addSamplePDF, " This function adds a sample PDF object to the analysis framework. The sample PDF object will be utilized in fitting procedures or likelihood scans. \n" - " *sample* A sample PDF object derived from samplePDFBase. " + " *sample* A sample PDF object derived from samplePDFBase. ", + py::arg("sample") ) ; diff --git a/python/manager.cpp b/python/manager.cpp index f2d41b4b6..b53fd1e4c 100644 --- a/python/manager.cpp +++ b/python/manager.cpp @@ -17,8 +17,8 @@ void initManager(py::module &m){ py::class_(m_manager, "Manager") .def( py::init(), - "create a Manager object with a specified *output_name", - py::arg("output_name") + "create a Manager object with a specified *config_file*", + py::arg("config_file") ) .def( From a9726c8f736e486dc3657a91b96945fb30406504 Mon Sep 17 00:00:00 2001 From: Ewan Date: Thu, 10 Oct 2024 03:36:11 -0700 Subject: [PATCH 04/24] add bindings for mcmc and minuit fitters --- python/fitter.cpp | 55 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/python/fitter.cpp b/python/fitter.cpp index fcb837c69..3c3c877e3 100644 --- a/python/fitter.cpp +++ b/python/fitter.cpp @@ -2,6 +2,8 @@ #include #include "mcmc/FitterBase.h" +#include "mcmc/mcmc.h" +#include "mcmc/MinuitFit.h" namespace py = pybind11; @@ -101,9 +103,60 @@ void initFitter(py::module &m){ " *sample* A sample PDF object derived from samplePDFBase. ", py::arg("sample") ) + + /* EM: Not sure if these are needed so just leave em commented for now + .def( + "add_syst_object", + &FitterBase::addSystObj, + " This function adds a Covariance object to the analysis framework. The Covariance object will be utilized in fitting procedures or likelihood scans. \n" + " *cov* A pointer to a Covariance object derived from covarianceBase. \n", + py::arg("cov") + ) + + .def( + "add_osc_handler", + py::overload_cast(&FitterBase::addOscHandler), + " Adds an oscillation handler for covariance objects. \n" + " *oscf* A pointer to a covarianceOsc object for forward oscillations. \n", + py::arg("oscf") + ) + + .def( + "add_osc_handler", + py::overload_cast(&FitterBase::addOscHandler), + " Adds an oscillation handler for covariance objects. \n" + " *osca* A pointer to a covarianceOsc object for the first oscillation. \n" + " *oscb* A pointer to a covarianceOsc object for the second oscillation. \n", + py::arg("osca"), + py::arg("oscb") + ) + */ + ; // End of FitterBase class binding + + + + py::class_(m_fitter, "MCMC") + .def(py::init()) + + .def( + "set_chain_length", + &mcmc::setChainLength, + "Set how long chain should be.", + py::arg("length") + ) - ; + .def( + "set_init_step_num", + &mcmc::setInitialStepNumber, + "Set initial step number, used when starting from another chain.", + py::arg("step_num") + ) + ; // end of MCMC class binding + py::class_(m_fitter, "MinuitFit") + .def(py::init()) + + ; // end of MinuitFit class binding } \ No newline at end of file From 7ac3896aae16919214b28f996fb44d27e1773c75 Mon Sep 17 00:00:00 2001 From: Ewan Date: Thu, 10 Oct 2024 03:36:29 -0700 Subject: [PATCH 05/24] add bindings for YAML stuff --- python/manager.cpp | 76 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 72 insertions(+), 4 deletions(-) diff --git a/python/manager.cpp b/python/manager.cpp index b53fd1e4c..497e881db 100644 --- a/python/manager.cpp +++ b/python/manager.cpp @@ -1,7 +1,10 @@ +// pybind includes #include #include - +// MaCh3 includes #include "manager/manager.h" +// yaml includes +#include "yaml-cpp/yaml.h" namespace py = pybind11; @@ -9,11 +12,70 @@ void initManager(py::module &m){ auto m_manager = m.def_submodule("manager"); m_manager.doc() = - "This module really just exists to hold the :py:class:`pyMaCh3.manager.Manager` class. The reason for this slightly weird structure is to mimic the structure of the c++ version of Mach3. \ + "This module handles the high level stuff like config options and YAML stuff. The main class is the :py:class:`pyMaCh3.manager.Manager` class. \ You can read more about the manager and config files on [the wiki page](https://github.com/mach3-software/MaCh3/wiki/01.-Manager-and-config-handling). \ Happy managing!"; - + // Bind some of the cpp-yaml library + // shamelessly stolen from stackoverflow: https://stackoverflow.com/questions/62347521/using-pybind11-to-wrap-yaml-cpp-iterator + py::enum_(m_manager, "NodeType") + .value("Undefined", YAML::NodeType::Undefined) + .value("Null", YAML::NodeType::Null) + .value("Scalar", YAML::NodeType::Scalar) + .value("Sequence", YAML::NodeType::Sequence) + .value("Map", YAML::NodeType::Map); + + py::class_(m_manager, "YamlNode") + .def(py::init()) + + .def("data", + [](const YAML::Node node){ + if ( node.Type() != YAML::NodeType::Scalar ) + { + throw MaCh3Exception(__FILE__, __LINE__, "Attempting to access the data of non-scalar yaml node. This is undefined."); + } + return node.Scalar(); + }) + + .def("__getitem__", + [](const YAML::Node node, const std::string& key){ + return node[key]; + }) + + .def("__getitem__", + [](const YAML::Node node, const int& key){ + if ( node.Type() != YAML::NodeType::Sequence) + { + throw MaCh3Exception(__FILE__, __LINE__, "Trying to access a non sequence yaml node with integer index"); + } + return node[key]; + }) + + .def("__iter__", + [](const YAML::Node &node) { + return py::make_iterator(node.begin(), node.end());}, + py::keep_alive<0, 1>()) + + .def("__str__", + [](const YAML::Node& node) { + YAML::Emitter out; + out << node; + return std::string(out.c_str()); + }) + + .def("type", &YAML::Node::Type) + + .def("__len__", &YAML::Node::size) + ; + + py::class_(m_manager, "_YamlDetailIteratorValue") + .def(py::init<>()) + .def("first", [](YAML::detail::iterator_value& val) { return val.first;}) + .def("second", [](YAML::detail::iterator_value& val) { return val.second;}) + ; + + m.def("load_file", &YAML::LoadFile, ""); + py::class_(m_manager, "Manager") .def( py::init(), @@ -26,6 +88,12 @@ void initManager(py::module &m){ &manager::Print, "Print currently used config." ) + + .def( + "raw", + &manager::raw, + "Get the raw yaml config." + ) ; -}; +} From 75635b52066fdd253e34e34c5150beb6ae6696c1 Mon Sep 17 00:00:00 2001 From: Ewan Date: Thu, 10 Oct 2024 04:25:28 -0700 Subject: [PATCH 06/24] make the documentation mode better --- Doc/sphinx/source/fitter.rst | 3 +++ Doc/sphinx/source/manager.rst | 44 ++++++++++++++++++++++++++++++++++ Doc/sphinx/source/plotting.md | 17 ------------- Doc/sphinx/source/plotting.rst | 21 +++++++++++++++- python/fitter.cpp | 4 +--- python/manager.cpp | 7 +++--- python/plotting.cpp | 2 +- 7 files changed, 72 insertions(+), 26 deletions(-) delete mode 100644 Doc/sphinx/source/plotting.md diff --git a/Doc/sphinx/source/fitter.rst b/Doc/sphinx/source/fitter.rst index 8e40a06cb..ed7756348 100644 --- a/Doc/sphinx/source/fitter.rst +++ b/Doc/sphinx/source/fitter.rst @@ -1,6 +1,9 @@ Fitter ======== +This module contains the various MaCh3 fitter algorithms which are available, as well as +the :py:class:`pyMaCh3.fitter.FitterBase` class which you can use to implement your own! + .. automodapi:: pyMaCh3.fitter :members: :undoc-members: diff --git a/Doc/sphinx/source/manager.rst b/Doc/sphinx/source/manager.rst index d04dbd315..54bebfea0 100644 --- a/Doc/sphinx/source/manager.rst +++ b/Doc/sphinx/source/manager.rst @@ -1,6 +1,50 @@ Manager ======= +This module handles the high level stuff like config options and YAML stuff. The main class is the :py:class:`pyMaCh3.manager.Manager` class. \ +You can read more about the manager and config files on [the wiki page](https://github.com/mach3-software/MaCh3/wiki/01.-Manager-and-config-handling). \ +YAML stuff works essentially the same as in the c++ version but with some caveats. +The main difference is that in the python version, the way that you access the actual data of a yaml node is different due to the way the python binding of the c++ code works. + +Lets take the following YAML snippet as an example :: + + Example: + Int: 1234 + Str: 'TestString' + IntArray: ['0', '1', '2'] + StrArray: ['val1', 'val2', 'val3'] + +In the c++ version you would access these by doing :: + + // for the integer + int testInt = node['Int'].as() + // for the string + std::string testStr = node['Str'].as() + // for the integer array + ... = node['IntArray'].as>() + // and for the string array + ... = node['StrArray'].as>() + +In the python version however things are different. Arrays are interpreted as arrays of YAML nodes and all of the underlying data are stored as strings. \ +So to access the data as above you would need to do :: + + test_int = int(node['Int'].data()) + test_str = node['Str'].data() + ## then for the arrays + int1 = int(node['IntArray'][0].data()) + int2 = int(node['IntArray'][1].data()) + int3 = int(node['IntArray'][2].data()) + + str1 = node['StrArray'][0].data() + str2 = node['StrArray'][1].data() + str3 = node['StrArray'][2].data() + +Parsing arrays can be made a bit less painful using list comprehension :: + + int_list = [int(i.data()) for i in node['IntArray']] + str_list = [i.data() for i in node['StrArray']] + + .. automodapi:: pyMaCh3.manager :members: :undoc-members: diff --git a/Doc/sphinx/source/plotting.md b/Doc/sphinx/source/plotting.md deleted file mode 100644 index feab558a2..000000000 --- a/Doc/sphinx/source/plotting.md +++ /dev/null @@ -1,17 +0,0 @@ -The plotting module can be used to make beautiful plots. See the [plotting wiki page](https://github.com/mach3-software/MaCh3/wiki/15.-Plotting) for information on how to configure the plotting library to work with your MaCh3 output files and other non-MaCh3 based fitters so you can compare results. - -The main class to worry about is :py:class:`pyMaCh3.plotting.PlottingManager` which provides the high level functionality and gives you access to everything else you should need. To use this in your plotting script simply do - -``` -## import the plotting module -from pyMach3 import plotting -## need sys to read command line arguments -import sys - -man = plotting.PlottingManager() -## give the command line arguments to the manager -manager.parse_inputs(sys.argv) - -## Now plot stuff!! -## ... -``` \ No newline at end of file diff --git a/Doc/sphinx/source/plotting.rst b/Doc/sphinx/source/plotting.rst index ada7b13d5..4dc5cd414 100644 --- a/Doc/sphinx/source/plotting.rst +++ b/Doc/sphinx/source/plotting.rst @@ -1,7 +1,26 @@ plotting ======== -.. mdinclude:: plotting.md +The plotting module can be used to make beautiful plots. See the [plotting wiki page](https://github.com/mach3-software/MaCh3/wiki/15.-Plotting) for information on how to configure the plotting library to work with your MaCh3 output files and other non-MaCh3 based fitters so you can compare results. + +The main class to worry about is :py:class:`pyMaCh3.plotting.PlottingManager` which provides the +high level functionality and gives you access to everything else you should need. + +To use this in your plotting script simply do :: + + ## import the plotting module + from pyMach3 import plotting + ## need sys to read command line arguments + import sys + + man = plotting.PlottingManager() + ## give the command line arguments to the manager + manager.parse_inputs(sys.argv) + + ## Now plot stuff!! + ## ... + + .. automodapi:: pyMaCh3.plotting :members: diff --git a/python/fitter.cpp b/python/fitter.cpp index 3c3c877e3..dcdd35bd7 100644 --- a/python/fitter.cpp +++ b/python/fitter.cpp @@ -37,9 +37,7 @@ void initFitter(py::module &m){ auto m_fitter = m.def_submodule("fitter"); m_fitter.doc() = - "This module contains the various MaCh3 fitter algorithms which are available, \ - as well as the :py:class:`pyMaCh3.fitter.FitterBase` class which you can use to \ - implement your own!"; + "This is a Python binding of MaCh3s C++ mcmc library."; py::class_(m_fitter, "FitterBase") diff --git a/python/manager.cpp b/python/manager.cpp index 497e881db..4bee35109 100644 --- a/python/manager.cpp +++ b/python/manager.cpp @@ -12,9 +12,7 @@ void initManager(py::module &m){ auto m_manager = m.def_submodule("manager"); m_manager.doc() = - "This module handles the high level stuff like config options and YAML stuff. The main class is the :py:class:`pyMaCh3.manager.Manager` class. \ - You can read more about the manager and config files on [the wiki page](https://github.com/mach3-software/MaCh3/wiki/01.-Manager-and-config-handling). \ - Happy managing!"; + "This is a Python binding of MaCh3s C++ based manager library."; // Bind some of the cpp-yaml library // shamelessly stolen from stackoverflow: https://stackoverflow.com/questions/62347521/using-pybind11-to-wrap-yaml-cpp-iterator @@ -35,7 +33,8 @@ void initManager(py::module &m){ throw MaCh3Exception(__FILE__, __LINE__, "Attempting to access the data of non-scalar yaml node. This is undefined."); } return node.Scalar(); - }) + }, + "Access the data stored in the node. This is only valid if the node is a 'scalar' type, i.e. it is a leaf of the yaml tree structure.") .def("__getitem__", [](const YAML::Node node, const std::string& key){ diff --git a/python/plotting.cpp b/python/plotting.cpp index 6538573bc..70b0c9c5c 100644 --- a/python/plotting.cpp +++ b/python/plotting.cpp @@ -15,7 +15,7 @@ namespace py = pybind11; void initPlotting(py::module &m){ auto m_plotting = m.def_submodule("plotting"); - m_plotting.doc() = "This is a Python binding of MaCh3s C++ based plotting library Library"; + m_plotting.doc() = "This is a Python binding of MaCh3s C++ based plotting library."; py::class_(m_plotting, "PlottingManager") .def( From b54ea8ab94f83bf6185e6954dbac8958c190c540 Mon Sep 17 00:00:00 2001 From: Ewan Date: Thu, 10 Oct 2024 06:10:41 -0700 Subject: [PATCH 07/24] add covarianceBase binding --- python/CMakeLists.txt | 1 + python/covariance.cpp | 105 ++++++++++++++++++++++++++++++++++++++++++ python/fitter.cpp | 19 ++++---- python/pyMaCh3.cpp | 2 + 4 files changed, 118 insertions(+), 9 deletions(-) create mode 100644 python/covariance.cpp diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 2b327fcc9..c20bea058 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -10,6 +10,7 @@ if( MaCh3_PYTHON_ENABLED ) fitter.cpp samplePDF.cpp manager.cpp + covariance.cpp ) ## EM: only works with code compiled with -fPIC enabled.. I think this flag can make things slightly slower ## so would be good to find a way around this. diff --git a/python/covariance.cpp b/python/covariance.cpp new file mode 100644 index 000000000..2cb4d2606 --- /dev/null +++ b/python/covariance.cpp @@ -0,0 +1,105 @@ +// pybind includes +#include +#include +// MaCh3 includes +#include "covariance/covarianceBase.h" + + +namespace py = pybind11; + +// As FitterBase is an abstract base class we have to do some gymnastics to get it to get it into python +class PyCovarianceBase : public covarianceBase { +public: + /* Inherit the constructors */ + using covarianceBase::covarianceBase; + + /* Trampoline (need one for each virtual function) */ + int CheckBounds() override { + PYBIND11_OVERRIDE_NAME( + int, /* Return type */ + covarianceBase, /* Parent class */ + "check_bounds", /* Name in python*/ + CheckBounds /* Name of function in C++ (must match Python name) */ + ); + } + + double GetLikelihood() override { + PYBIND11_OVERRIDE_NAME( + double, /* Return type */ + covarianceBase, /* Parent class */ + "get_likelihood", /* Name in python*/ + GetLikelihood /* Name of function in C++ (must match Python name) */ + ); + } + + double getNominal(int i) override { + PYBIND11_OVERRIDE_NAME( + double, /* Return type */ + covarianceBase, /* Parent class */ + "get_nominal", /* Name in python*/ + getNominal, /* Name of function in C++ (must match Python name) */ + i /* Arguments*/ + ); + } + + void proposeStep() override { + PYBIND11_OVERRIDE_NAME( + void, /* Return type */ + covarianceBase, /* Parent class */ + "propose_step", /* Name in python*/ + proposeStep /* Name of function in C++ (must match Python name) */ + ); + } + + std::vector getNominalArray() override { + PYBIND11_OVERRIDE_NAME( + std::vector, /* Return type */ + covarianceBase, /* Parent class */ + "get_nominal_array", /* Name in python*/ + getNominalArray /* Name of function in C++ (must match Python name) */ + ); + } +}; + + +void initCovariance(py::module &m){ + + auto m_covariance = m.def_submodule("covariance"); + m_covariance.doc() = + "This is a Python binding of MaCh3s C++ covariance library."; + + + py::class_(m_covariance, "CovarianceBase") + .def( + py::init&, const char *, double, int, int>(), + "Construct a covariance object from a yaml file \n\ + :param yaml_file: The name of the yaml file to initialise from. \n\ + :param name: the name of this covariance object. \n\ + :param threshold: threshold PCA threshold from 0 to 1. Default is -1 and means no PCA. \n\ + :param first_PCA_par: FirstPCAdpar First PCA parameter that will be decomposed. \n\ + :param last_PCA_par: LastPCAdpar First PCA parameter that will be decomposed.", + py::arg("yaml_file"), + py::arg("name"), + py::arg("threshold") = -1.0, + py::arg("firs_PCA_par") = -999, + py::arg("last_PCA_par") = -999 + ) + + .def( + "calculate_likelihood", + &covarianceBase::CalcLikelihood, + "Calculate penalty term based on inverted covariance matrix." + ) + + .def( + "throw_par_prop", + &covarianceBase::throwParProp, + "Throw the proposed parameter by magnitude *mag* X sigma. \n\ + :param mag: This value multiplied by the prior value of each parameter will be the width of the distribution that the parameter values are drawn from. ", + py::arg("mag") = 1.0 + ) + + ; + + +} diff --git a/python/fitter.cpp b/python/fitter.cpp index dcdd35bd7..e452ea67d 100644 --- a/python/fitter.cpp +++ b/python/fitter.cpp @@ -1,6 +1,7 @@ +// pybind includes #include #include - +// MaCh3 includes #include "mcmc/FitterBase.h" #include "mcmc/mcmc.h" #include "mcmc/MinuitFit.h" @@ -74,21 +75,21 @@ void initFitter(py::module &m){ "run_2d_LLH_scan", &FitterBase::Run2DLLHScan, " Perform a 2D likelihood scan. \n" - " *warning* This operation may take a significant amount of time, especially for complex models." + " :param warning: This operation may take a significant amount of time, especially for complex models." ) .def( "run_sigma_var", &FitterBase::RunSigmaVar, " Perform a 2D and 1D sigma var for all samples. \n" - " *warning* Code uses TH2Poly" + " :param warning: Code uses TH2Poly" ) .def( "drag_race", &FitterBase::DragRace, " Calculates the required time for each sample or covariance object in a drag race simulation. Inspired by Dan's feature \n" - " *NLaps* number of laps, every part of Fitter will be tested with given number of laps and you will get total and average time", + " :param NLaps: number of laps, every part of Fitter will be tested with given number of laps and you will get total and average time", py::arg("NLaps") = 100 ) @@ -98,7 +99,7 @@ void initFitter(py::module &m){ "add_sample_PDF", &FitterBase::addSamplePDF, " This function adds a sample PDF object to the analysis framework. The sample PDF object will be utilized in fitting procedures or likelihood scans. \n" - " *sample* A sample PDF object derived from samplePDFBase. ", + " :param sample: A sample PDF object derived from samplePDFBase. ", py::arg("sample") ) @@ -107,7 +108,7 @@ void initFitter(py::module &m){ "add_syst_object", &FitterBase::addSystObj, " This function adds a Covariance object to the analysis framework. The Covariance object will be utilized in fitting procedures or likelihood scans. \n" - " *cov* A pointer to a Covariance object derived from covarianceBase. \n", + " :param cov: A pointer to a Covariance object derived from covarianceBase. \n", py::arg("cov") ) @@ -115,7 +116,7 @@ void initFitter(py::module &m){ "add_osc_handler", py::overload_cast(&FitterBase::addOscHandler), " Adds an oscillation handler for covariance objects. \n" - " *oscf* A pointer to a covarianceOsc object for forward oscillations. \n", + " :param oscf: A pointer to a covarianceOsc object for forward oscillations. \n", py::arg("oscf") ) @@ -123,8 +124,8 @@ void initFitter(py::module &m){ "add_osc_handler", py::overload_cast(&FitterBase::addOscHandler), " Adds an oscillation handler for covariance objects. \n" - " *osca* A pointer to a covarianceOsc object for the first oscillation. \n" - " *oscb* A pointer to a covarianceOsc object for the second oscillation. \n", + " :param osca: A pointer to a covarianceOsc object for the first oscillation. \n" + " :param oscb: A pointer to a covarianceOsc object for the second oscillation. \n", py::arg("osca"), py::arg("oscb") ) diff --git a/python/pyMaCh3.cpp b/python/pyMaCh3.cpp index 6107c37b3..35a8f2625 100644 --- a/python/pyMaCh3.cpp +++ b/python/pyMaCh3.cpp @@ -7,10 +7,12 @@ void initPlotting(py::module &); // <- defined in python/plotting.cpp void initFitter(py::module &); // <- defined in python/fitter.cpp void initSamplePDF(py::module &); // <- defined in python/samplePDF.cpp void initManager(py::module &); // <- defined in python/manager.cpp +void initCovariance(py::module &); // <- defined in python/covariance.cpp PYBIND11_MODULE( pyMaCh3, m ) { initPlotting(m); initFitter(m); initSamplePDF(m); initManager(m); + initCovariance(m); } \ No newline at end of file From 642653488bb6a24a26549f38a63b3beda013ec30 Mon Sep 17 00:00:00 2001 From: Ewan Date: Thu, 10 Oct 2024 06:50:11 -0700 Subject: [PATCH 08/24] add bindings for covariance objects and can now run MCMC fitter from pythongit status --- python/covariance.cpp | 45 ++++++++++++++++++++++++++++++++++++++----- python/fitter.cpp | 3 +-- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/python/covariance.cpp b/python/covariance.cpp index 2cb4d2606..bec497f75 100644 --- a/python/covariance.cpp +++ b/python/covariance.cpp @@ -3,11 +3,13 @@ #include // MaCh3 includes #include "covariance/covarianceBase.h" +#include "covariance/covarianceXsec.h" +#include "covariance/covarianceOsc.h" namespace py = pybind11; -// As FitterBase is an abstract base class we have to do some gymnastics to get it to get it into python +// As CovarianceBase is an abstract base class we have to do some gymnastics to get it to get it into python class PyCovarianceBase : public covarianceBase { public: /* Inherit the constructors */ @@ -72,13 +74,13 @@ void initCovariance(py::module &m){ py::class_(m_covariance, "CovarianceBase") .def( py::init&, const char *, double, int, int>(), - "Construct a covariance object from a yaml file \n\ - :param yaml_file: The name of the yaml file to initialise from. \n\ + "Construct a covariance object from a set of yaml files that define the systematic parameters \n\ + :param yaml_files: The name of the yaml file to initialise from. \n\ :param name: the name of this covariance object. \n\ :param threshold: threshold PCA threshold from 0 to 1. Default is -1 and means no PCA. \n\ :param first_PCA_par: FirstPCAdpar First PCA parameter that will be decomposed. \n\ :param last_PCA_par: LastPCAdpar First PCA parameter that will be decomposed.", - py::arg("yaml_file"), + py::arg("yaml_files"), py::arg("name"), py::arg("threshold") = -1.0, py::arg("firs_PCA_par") = -999, @@ -98,8 +100,41 @@ void initCovariance(py::module &m){ :param mag: This value multiplied by the prior value of each parameter will be the width of the distribution that the parameter values are drawn from. ", py::arg("mag") = 1.0 ) + ; // End of CovarianceBase binding - ; + py::class_(m_covariance, "CovarianceXsec") + .def( + py::init&, const char *, double, int, int>(), + "Construct a systematic covariance object from a set of yaml files that define the systematic parameters \n\ + :param yaml_files: The name of the yaml file to initialise from. \n\ + :param name: the name of this covariance object. \n\ + :param threshold: threshold PCA threshold from 0 to 1. Default is -1 and means no PCA. \n\ + :param first_PCA_par: FirstPCAdpar First PCA parameter that will be decomposed. \n\ + :param last_PCA_par: LastPCAdpar First PCA parameter that will be decomposed.", + py::arg("yaml_files"), + py::arg("name") = "xsec_cov", + py::arg("threshold") = -1.0, + py::arg("firs_PCA_par") = -999, + py::arg("last_PCA_par") = -999 + ) + ; // End of CovarianceXsec binding + + py::class_(m_covariance, "CovarianceOsc") + .def( + py::init&, const char *, double, int, int>(), + "Construct a oscillation covariance object from a set of yaml files that define the systematic parameters \n\ + :param yaml_files: The name of the yaml file to initialise from. \n\ + :param name: the name of this covariance object. \n\ + :param threshold: threshold PCA threshold from 0 to 1. Default is -1 and means no PCA. \n\ + :param first_PCA_par: FirstPCAdpar First PCA parameter that will be decomposed. \n\ + :param last_PCA_par: LastPCAdpar First PCA parameter that will be decomposed.", + py::arg("yaml_files"), + py::arg("name") = "xsec_cov", + py::arg("threshold") = -1.0, + py::arg("firs_PCA_par") = -999, + py::arg("last_PCA_par") = -999 + ) + ; // End of CovarianceXsec binding } diff --git a/python/fitter.cpp b/python/fitter.cpp index e452ea67d..ea1870c36 100644 --- a/python/fitter.cpp +++ b/python/fitter.cpp @@ -103,7 +103,6 @@ void initFitter(py::module &m){ py::arg("sample") ) - /* EM: Not sure if these are needed so just leave em commented for now .def( "add_syst_object", &FitterBase::addSystObj, @@ -129,7 +128,7 @@ void initFitter(py::module &m){ py::arg("osca"), py::arg("oscb") ) - */ + ; // End of FitterBase class binding From 7c88f15c5b3774d51d009ec7a69bd8ed763ac027 Mon Sep 17 00:00:00 2001 From: Ewan Date: Thu, 10 Oct 2024 07:17:10 -0700 Subject: [PATCH 09/24] MORE documentation --- Doc/sphinx/source/fitter.rst | 2 +- Doc/sphinx/source/pyMaCh3.rst | 1 + python/fitter.cpp | 7 +++---- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Doc/sphinx/source/fitter.rst b/Doc/sphinx/source/fitter.rst index ed7756348..c2126795b 100644 --- a/Doc/sphinx/source/fitter.rst +++ b/Doc/sphinx/source/fitter.rst @@ -1,5 +1,5 @@ Fitter -======== +====== This module contains the various MaCh3 fitter algorithms which are available, as well as the :py:class:`pyMaCh3.fitter.FitterBase` class which you can use to implement your own! diff --git a/Doc/sphinx/source/pyMaCh3.rst b/Doc/sphinx/source/pyMaCh3.rst index 750ee0e22..2b2dbbd5c 100644 --- a/Doc/sphinx/source/pyMaCh3.rst +++ b/Doc/sphinx/source/pyMaCh3.rst @@ -7,4 +7,5 @@ pyMaCh3 manager.rst sample-pdf.rst fitter.rst + covariance.rst plotting.rst \ No newline at end of file diff --git a/python/fitter.cpp b/python/fitter.cpp index ea1870c36..269dac3f0 100644 --- a/python/fitter.cpp +++ b/python/fitter.cpp @@ -53,10 +53,9 @@ void initFitter(py::module &m){ .def( "GetName", &FitterBase::GetName, - " The name of the algorithm, you should override this with something like \n" - "``` \n" - "return 'mySuperCoolAlgoName' \n" - "``` \n" + " The name of the algorithm, you should override this with something like:: \n" + "\n" + " return 'mySuperCoolAlgoName' \n" ) .def( From b8263c8999b10b6d27a08a45ad48c78cb253aea5 Mon Sep 17 00:00:00 2001 From: Ewan Date: Thu, 10 Oct 2024 07:17:40 -0700 Subject: [PATCH 10/24] forgot to add this... --- Doc/sphinx/source/covariance.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 Doc/sphinx/source/covariance.rst diff --git a/Doc/sphinx/source/covariance.rst b/Doc/sphinx/source/covariance.rst new file mode 100644 index 000000000..ff2fceab2 --- /dev/null +++ b/Doc/sphinx/source/covariance.rst @@ -0,0 +1,10 @@ +Covariance +========== + +This module contains the covariance objetcs which Mach3 uses to deal with systematic parameters. It also includes +the :py:class:`pyMaCh3.covariance.CovarianceBase` class which you can use to implement your own! + +.. automodapi:: pyMaCh3.covariance + :members: + :undoc-members: + :show-inheritance: \ No newline at end of file From a44219b1ddfffcc3bdf5635765f898862efb9a53 Mon Sep 17 00:00:00 2001 From: Ewan Date: Fri, 11 Oct 2024 09:36:54 -0700 Subject: [PATCH 11/24] would you believe it, more bindings --- python/CMakeLists.txt | 1 + python/pyMaCh3.cpp | 2 + python/splines.cpp | 228 +++++++++++++++++++++++++++++++++++++++ splines/SplineMonolith.h | 3 + 4 files changed, 234 insertions(+) create mode 100644 python/splines.cpp diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index c20bea058..dc56a035f 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -11,6 +11,7 @@ if( MaCh3_PYTHON_ENABLED ) samplePDF.cpp manager.cpp covariance.cpp + splines.cpp ) ## EM: only works with code compiled with -fPIC enabled.. I think this flag can make things slightly slower ## so would be good to find a way around this. diff --git a/python/pyMaCh3.cpp b/python/pyMaCh3.cpp index 35a8f2625..97e225538 100644 --- a/python/pyMaCh3.cpp +++ b/python/pyMaCh3.cpp @@ -8,6 +8,7 @@ void initFitter(py::module &); // <- defined in python/fitter.cpp void initSamplePDF(py::module &); // <- defined in python/samplePDF.cpp void initManager(py::module &); // <- defined in python/manager.cpp void initCovariance(py::module &); // <- defined in python/covariance.cpp +void initSplines(py::module &); // <- defined in python/splines.cpp PYBIND11_MODULE( pyMaCh3, m ) { initPlotting(m); @@ -15,4 +16,5 @@ PYBIND11_MODULE( pyMaCh3, m ) { initSamplePDF(m); initManager(m); initCovariance(m); + initSplines(m); } \ No newline at end of file diff --git a/python/splines.cpp b/python/splines.cpp new file mode 100644 index 000000000..e20157f51 --- /dev/null +++ b/python/splines.cpp @@ -0,0 +1,228 @@ +// pybind includes +#include +#include +#include +// MaCh3 includes +#include "splines/SplineBase.h" +#include "splines/SplineMonolith.h" +#include "splines/SplineStructs.h" +#include "samplePDF/Structs.h" // <- The spline stuff that's in here should really be moved to splineStructs.h but I ain't doing that right now +// ROOT includes +#include "TSpline.h" + +namespace py = pybind11; + +// As SplineBase is an abstract base class we have to do some gymnastics to get it to get it into python +class PySplineBase : public SplineBase { +public: + /* Inherit the constructors */ + using SplineBase::SplineBase; + + /* Trampoline (need one for each virtual function) */ + void Evaluate() override { + PYBIND11_OVERRIDE_PURE_NAME( + void, /* Return type */ + SplineBase, /* Parent class */ + "evaluate", /* Name in python*/ + Evaluate /* Name of function in C++ (must match Python name) */ + ); + } + + std::string GetName() const override { + PYBIND11_OVERRIDE_NAME( + std::string, /* Return type */ + SplineBase, /* Parent class */ + "get_name", /* Name in python*/ + GetName /* Name of function in C++ (must match Python name) */ + ); + } + + void FindSplineSegment() override { + PYBIND11_OVERRIDE_PURE_NAME( + void, /* Return type */ + SplineBase, /* Parent class */ + "find_segment", /* Name in python*/ + FindSplineSegment /* Name of function in C++ (must match Python name) */ + ); + } + + void CalcSplineWeights() override { + PYBIND11_OVERRIDE_PURE_NAME( + void, /* Return type */ + SplineBase, /* Parent class */ + "calculate_weights", /* Name in python*/ + CalcSplineWeights /* Name of function in C++ (must match Python name) */ + ); + } + + void ModifyWeights() override { + PYBIND11_OVERRIDE_PURE_NAME( + void, /* Return type */ + SplineBase, /* Parent class */ + "modify_weights", /* Name in python*/ + ModifyWeights /* Name of function in C++ (must match Python name) */ + ); + } +}; + + +void initSplines(py::module &m){ + + auto m_splines = m.def_submodule("splines"); + m_splines.doc() = + "This is a Python binding of MaCh3s C++ based spline library."; + + // Bind the interpolation type enum that lets us set different interpolation types for our splines + py::enum_(m_splines, "InterpolationType") + .value("Linear", SplineInterpolation::kLinear) + .value("Linear_Func", SplineInterpolation::kLinearFunc) + .value("Cubic_TSpline3", SplineInterpolation::kTSpline3) + .value("Cubic_Monotonic", SplineInterpolation::kMonotonic) + .value("Cubic_Akima", SplineInterpolation::kAkima) + .value("N_Interpolation_Types", SplineInterpolation::kSplineInterpolations); + + + py::class_(m_splines, "SplineBase"); + + py::class_(m_splines, "_ResponseFunctionBase") + .doc() = "Base class of the response function, this binding only exists for consistency with the inheritance structure of the c++ code. Just pretend it doesn't exist and don't worry about it..."; + + // Bind the TSpline3_red class. Decided to go with a clearer name of ResponseFunction for the python binding + // and make the interface a bit more python-y. Additionally remove passing root stuff so we don't need to deal + // with root python binding and can just pass it native python objects. + py::class_(m_splines, "ResponseFunction") + .def( + // define a more python friendly constructor that massages the inputs and passes them + // through to the c++ constructor + py::init + ( + // Just take in some vectors, then build a TSpline3 and pass this to the constructor + [](std::vector xVals, std::vector yVals, SplineInterpolation interpType) + { + if ( xVals.size() != yVals.size() ) + { + throw MaCh3Exception(__FILE__, __LINE__, "Different number of x values and y values!"); + } + + int length = xVals.size(); + + TSpline3 *splineTmp = new TSpline3( "spline_tmp", xVals.data(), yVals.data(), length ); + + return std::make_unique(splineTmp, interpType); + } + ) + ) + + .def( + "find_segment", + &TSpline3_red::FindX, + "Find the segment that a particular *value* lies in. \n" + ":param value: The value to test", + py::arg("value") + ) + + .def( + "evaluate", + &TSpline3_red::Eval, + "Evaluate the response function at a particular *value*. \n" + ":param value: The value to evaluate at.", + py::arg("value") + ) + ; // End of binding for ResponseFunction + + py::class_(m_splines, "EventSplineMonolith") + .def( + py::init( + [](std::vector> &responseFns, const bool saveFlatTree) + { + std::vector respFnTypes; + for(int i = 0; i > responseFns.size(); i++) + { + // ** WARNING ** + // Right now I'm only pushing back TSpline3_reds as thats all thats supported right now + // In the future there might be more + // I think what would be best to do would be to store the interpolation type somehow in the ResponseFunction objects + // then just read them here and pass through to the constructor + respFnTypes.push_back(RespFuncType::kTSpline3_red); + } + return std::make_unique(responseFns, respFnTypes, saveFlatTree); + } + ), + "Create an EventSplineMonolith \n" + ":param master_splines: These are the 'knot' values to make splines from. \n" + ":param save_flat_tree: Whether we want to save monolith into speedy flat tree", + py::arg("master_splines"), + py::arg("save_flat_tree") = false + ) + + .def( + py::init(), + "Constructor where you pass path to preprocessed root FileName which is generated by creating an EventSplineMonolith with the `save_flat_tree` flag set to True. \n" + ":param file_name: The name of the file to read from.", + py::arg("file_name") + ) + + .def( + "evaluate", + &SMonolith::Evaluate, + "Evaluate the splines at their current values." + ) + + .def( + "sync_mem_transfer", + &SMonolith::SynchroniseMemTransfer, + "This is important when running on GPU. After calculations are done on GPU we copy memory to CPU. This operation is asynchronous meaning while memory is being copied some operations are being carried. Memory must be copied before actual reweight. This function make sure all has been copied." + ) + + .def( + "get_event_weight", + &SMonolith::retPointer, + py::return_value_policy::reference, + "Get the weight of a particular event. \n" + ":param event: The index of the event whose weight you would like.", + py::arg("event") + ) + + .def( + "set_param_value_array", + // Wrap up the setSplinePointers method so that we can take in a numpy array and get + // pointers to it's sweet sweet data and use those pointers in the splineMonolith + [](SMonolith &self, py::array_t &array) + { + py::buffer_info bufInfo = array.request(); + + if ( bufInfo.ndim != 1) + { + throw MaCh3Exception(__FILE__, __LINE__, "Number of dimensions in parameter array must be one!"); + } + + if ( bufInfo.shape[0] != self.GetNParams() ) + { + throw MaCh3Exception(__FILE__, __LINE__, "Number of entries in parameter array must equal the number of parameters!"); + } + + std::vector paramVec; + paramVec.resize(self.GetNParams()); + + for( int idx = 0; idx < self.GetNParams(); idx++ ) + { + // booooo pointer arithmetic + paramVec[idx] = array.data() + idx; + } + + self.setSplinePointers(paramVec); + }, + "Set the array that the monolith should use to read parameter values from. \n" + "Usage of this might vary a bit from what you're used to in python. \n" + "Rather than just setting the values here, what you're really doing is setting pointers in the underlying c++ code. \n" + "What that means is that you pass an array to this function like:: \n" + "\n event_spline_monolith_instance.set_param_value_array(array) \n\n" + "Then when you set values in that array as normal, they will also be updated inside of the event_spline_monolith_instance.", + py::arg("array") + + ) + + .doc() = "This 'monolith' deals with event by event weighting using splines." + + ; // End of binding for EventSplineMonolith +} \ No newline at end of file diff --git a/splines/SplineMonolith.h b/splines/SplineMonolith.h index b744d212e..f7a08b51c 100644 --- a/splines/SplineMonolith.h +++ b/splines/SplineMonolith.h @@ -28,6 +28,9 @@ class SMonolith : public SplineBase { /// @brief Get class name inline std::string GetName()const {return "SplineMonolith";}; + /// @brief Get number of spline parameters + short int GetNParams()const {return nParams;}; + /// @brief KS: After calculations are done on GPU we copy memory to CPU. This operation is asynchronous meaning while memory is being copied some operations are being carried. Memory must be copied before actual reweight. This function make sure all has been copied. void SynchroniseMemTransfer(); From 52dd462ad65a4821201d7eea544660cbee4a0045 Mon Sep 17 00:00:00 2001 From: Ewan Date: Mon, 14 Oct 2024 05:31:59 -0700 Subject: [PATCH 12/24] fix spline monolith and make it now usable in python! --- python/splines.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/python/splines.cpp b/python/splines.cpp index e20157f51..1e80bfb34 100644 --- a/python/splines.cpp +++ b/python/splines.cpp @@ -90,7 +90,7 @@ void initSplines(py::module &m){ // Bind the TSpline3_red class. Decided to go with a clearer name of ResponseFunction for the python binding // and make the interface a bit more python-y. Additionally remove passing root stuff so we don't need to deal // with root python binding and can just pass it native python objects. - py::class_(m_splines, "ResponseFunction") + py::class_>(m_splines, "ResponseFunction") .def( // define a more python friendly constructor that massages the inputs and passes them // through to the c++ constructor @@ -108,7 +108,7 @@ void initSplines(py::module &m){ TSpline3 *splineTmp = new TSpline3( "spline_tmp", xVals.data(), yVals.data(), length ); - return std::make_unique(splineTmp, interpType); + return new TSpline3_red(splineTmp, interpType); } ) ) @@ -136,7 +136,7 @@ void initSplines(py::module &m){ [](std::vector> &responseFns, const bool saveFlatTree) { std::vector respFnTypes; - for(int i = 0; i > responseFns.size(); i++) + for(int i = 0; i < responseFns[0].size(); i++) { // ** WARNING ** // Right now I'm only pushing back TSpline3_reds as thats all thats supported right now @@ -145,11 +145,11 @@ void initSplines(py::module &m){ // then just read them here and pass through to the constructor respFnTypes.push_back(RespFuncType::kTSpline3_red); } - return std::make_unique(responseFns, respFnTypes, saveFlatTree); + return new SMonolith(responseFns, respFnTypes, saveFlatTree); } ), "Create an EventSplineMonolith \n" - ":param master_splines: These are the 'knot' values to make splines from. \n" + ":param master_splines: These are the 'knot' values to make splines from. This should be an P x E 2D list where P is the number of parameters and E is the number of events. \n" ":param save_flat_tree: Whether we want to save monolith into speedy flat tree", py::arg("master_splines"), py::arg("save_flat_tree") = false From b6bdf91f75a7695f4a176655395a47a56c162128 Mon Sep 17 00:00:00 2001 From: Ewan Date: Mon, 14 Oct 2024 12:36:40 -0700 Subject: [PATCH 13/24] Fix the memory issues with splines! It was the single knot splines causing memory weirdness when building TSpline3s. We can get around this by... not doing that --- python/splines.cpp | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/python/splines.cpp b/python/splines.cpp index 1e80bfb34..fd3d47f2c 100644 --- a/python/splines.cpp +++ b/python/splines.cpp @@ -97,7 +97,7 @@ void initSplines(py::module &m){ py::init ( // Just take in some vectors, then build a TSpline3 and pass this to the constructor - [](std::vector xVals, std::vector yVals, SplineInterpolation interpType) + [](std::vector &xVals, std::vector &yVals, SplineInterpolation interpType) { if ( xVals.size() != yVals.size() ) { @@ -106,9 +106,30 @@ void initSplines(py::module &m){ int length = xVals.size(); - TSpline3 *splineTmp = new TSpline3( "spline_tmp", xVals.data(), yVals.data(), length ); + if (length == 1) + { + _float_ *pars[3]; + pars[0] = new double(0.0); + pars[1] = new double(0.0); + pars[2] = new double(0.0); + return new TSpline3_red(xVals.data(), yVals.data(), 1, pars); + + delete[] pars; + } + else + { + std::cout << "CALLING TSPline3 Constructor with " << length << " knots!" << std::endl; + for ( int i = 0; i < length; i++) + { + std:: cout << "{" << xVals[i] << ", " << yVals[i] << "} "; + } + std::cout << std::endl; + + TSpline3 *splineTmp = new TSpline3( "spline_tmp", xVals.data(), yVals.data(), length ); - return new TSpline3_red(splineTmp, interpType); + std::cout << "Spline constructed successfully!" << std::endl << std::endl; + return new TSpline3_red(splineTmp, interpType); + } } ) ) From c8cb37439b11f0077d71c572f083c29449c89b26 Mon Sep 17 00:00:00 2001 From: Ewan Date: Tue, 15 Oct 2024 05:51:09 -0700 Subject: [PATCH 14/24] some additional function bindings and some extra c++ getters for covariance and samplePDF objects. Can now juuuust about build a samplePDF class in python! --- covariance/covarianceBase.h | 4 ++ covariance/covarianceXsec.h | 3 + python/covariance.cpp | 91 +++++++++++++++++++++++ python/samplePDF.cpp | 139 ++++++++++++++++++++++++++++++++++-- 4 files changed, 232 insertions(+), 5 deletions(-) diff --git a/covariance/covarianceBase.h b/covariance/covarianceBase.h index 04967e8e4..11291bab5 100644 --- a/covariance/covarianceBase.h +++ b/covariance/covarianceBase.h @@ -190,6 +190,10 @@ class covarianceBase { /// element of a new array. There must be a clever C++ way to be careful inline const double* retPointer(const int iParam) {return &(_fPropVal.data()[iParam]);} + /// @brief Get a reference to the proposed parameter values + /// Can be useful if you want to track these without having to copy values using getProposed() + inline const std::vector &getParPropVec() {return _fPropVal;} + //Some Getters /// @brief Get total number of parameters inline int GetNumParams() {return _fNumPar;} diff --git a/covariance/covarianceXsec.h b/covariance/covarianceXsec.h index 828f95929..067454327 100644 --- a/covariance/covarianceXsec.h +++ b/covariance/covarianceXsec.h @@ -32,6 +32,9 @@ class covarianceXsec : public covarianceBase { /// @brief Get interpolation type for a given parameter /// @param i spline parameter index, not confuse with global index inline SplineInterpolation GetParSplineInterpolation(const int i) {return SplineParams.at(i)._SplineInterpolationType;} + /// @brief Get the name of the spline associated with the spline at index i + /// @param i spline parameter index, not to be confused with global index + std::string GetParSplineName(const int i) {return _fSplineNames[i];} //DB Get spline parameters depending on given DetID const std::vector GetGlobalSystIndexFromDetID(const int DetID, const SystType Type); diff --git a/python/covariance.cpp b/python/covariance.cpp index bec497f75..fc57af4ec 100644 --- a/python/covariance.cpp +++ b/python/covariance.cpp @@ -1,6 +1,7 @@ // pybind includes #include #include +#include // MaCh3 includes #include "covariance/covarianceBase.h" #include "covariance/covarianceXsec.h" @@ -70,6 +71,14 @@ void initCovariance(py::module &m){ m_covariance.doc() = "This is a Python binding of MaCh3s C++ covariance library."; + + // Bind the systematic type enum that lets us set different types of systematics + py::enum_(m_covariance, "SystematicType") + .value("Normalisation", SystType::kNorm) + .value("Spline", SystType::kSpline) + .value("Functional", SystType::kFunc) + .value("N_Systematic_Types", SystType::kSystTypes); + py::class_(m_covariance, "CovarianceBase") .def( @@ -100,6 +109,64 @@ void initCovariance(py::module &m){ :param mag: This value multiplied by the prior value of each parameter will be the width of the distribution that the parameter values are drawn from. ", py::arg("mag") = 1.0 ) + + .def( + "get_internal_par_name", + [](covarianceBase &self, int index) + { + // do this to disambiguate between the std::string and const char* version of this fn + std::string ret; + ret = self.GetParName(index); + return ret; + }, + "Get the internally used name of this parameter. \n\ + :param index: The global index of the parameter", + py::arg("index") + ) + + .def( + "get_fancy_par_name", + [](covarianceBase &self, int index) + { + // do this to disambiguate between the std::string and const char* version of this fn + std::string ret; + ret = self.GetParFancyName(index); + return ret; + }, + "Get the name of this parameter. \n\ + :param index: The global index of the parameter", + py::arg("index") + ) + + .def( + "get_n_pars", + &covarianceBase::getNpars, + "Get the number of parameters that this covariance object knows about." + ) + + .def( + "propose_step", + &covarianceBase::proposeStep, + "Propose a step based on the covariances. Also feel free to overwrite if you want something more funky." + ) + + .def( + "get_proposal_array", + [](covarianceBase &self) + { + return py::memoryview::from_buffer( + self.getParPropVec().data(), // the data pointer + {self.getNpars()}, // shape + {sizeof(double)} // shape + ); + }, + "Bind a python array to the parameter proposal values for this covariance object. \n\ + This allows you to set e.g. a numpy array to 'track' the parameter proposal values. You could either use this to directly set the proposals, or to just read the values proposed by e.g. throw_par_prop() \n\ + :warning: This should be set *AFTER* all of the parameters have been read in from the config file as it resizes the array to fit the number of parameters. \n\ + :param array: This is the array that will be set. Size and contents don't matter as it will be changed to fit the parameters. " + ) + + ; // End of CovarianceBase binding @@ -118,6 +185,30 @@ void initCovariance(py::module &m){ py::arg("firs_PCA_par") = -999, py::arg("last_PCA_par") = -999 ) + + .def( + "get_par_type", + &covarianceXsec::GetParamType, + "Get what type of systematic this parameters is (see :py:enum:`pyMaCh3.covarianc.SystematicType` for possible types). \n\ + :param index: The global index of the parameter", + py::arg("index") + ) + + .def( + "get_par_spline_type", + &covarianceXsec::GetParSplineInterpolation, + "Get what type of spline this parameter is set to use (assuming that it is a spline type parameter). \n\ + :param index: The index of the spline parameter", + py::arg("index") + ) + + .def( + "get_par_spline_name", + &covarianceXsec::GetParSplineName, + "Get the name of the spline associated with a spline parameter. This is generally what it is called in input spline files and can in principle be different to the parameters name. \n\ + :param index: The index of the spline parameter", + py::arg("index") + ) ; // End of CovarianceXsec binding py::class_(m_covariance, "CovarianceOsc") diff --git a/python/samplePDF.cpp b/python/samplePDF.cpp index be4536503..8c3fee304 100644 --- a/python/samplePDF.cpp +++ b/python/samplePDF.cpp @@ -1,8 +1,10 @@ #include #include +#include #include "samplePDF/samplePDFBase.h" #include "samplePDF/samplePDFFDBase.h" +#include "samplePDF/FDMCStruct.h" namespace py = pybind11; @@ -50,17 +52,100 @@ class PySamplePDFBase : public samplePDFBase { } }; + +// As SamplePDFFDBase is an abstract base class we have to do some gymnastics to get it to get it into python +class PySamplePDFFDBase : public samplePDFFDBase { +public: + /* Inherit the constructors */ + using samplePDFFDBase::samplePDFFDBase; + + /* Trampoline (need one for each virtual function) */ + void SetupWeightPointers() override { + PYBIND11_OVERRIDE_PURE_NAME( + void, /* Return type */ + samplePDFFDBase, /* Parent class */ + "setup_weight_pointers", /*python name*/ + SetupWeightPointers, /* Name of function in C++ */ + /* Argument(s) */ + ); + } + + double ReturnKinematicParameter(std::string, int, int) override { + PYBIND11_OVERRIDE_PURE_NAME( + double, /* Return type */ + samplePDFFDBase, /* Parent class */ + "get_event_kinematic_value",/* python name*/ + ReturnKinematicParameter, /* Name of function in C++ (must match Python name) */ + py::arg("variable"), + py::arg("sample"), + py::arg("event") /* Argument(s) */ + ); + } + double ReturnKinematicParameter(double, int, int) override { + PYBIND11_OVERRIDE_PURE_NAME( + double, /* Return type */ + samplePDFFDBase, /* Parent class */ + "get_event_kinematic_value",/* python name*/ + ReturnKinematicParameter, /* Name of function in C++ (must match Python name) */ + py::arg("variable"), + py::arg("sample"), + py::arg("event") /* Argument(s) */ + ); + } + + const double *ReturnKinematicParameterByReference(std::string, int, int) override { + PYBIND11_OVERRIDE_PURE_NAME( + const double *, /* Return type */ + samplePDFFDBase, /* Parent class */ + "get_event_kinematic_value_reference",/* python name*/ + ReturnKinematicParameterByReference, /* Name of function in C++ (must match Python name) */ + py::arg("variable"), + py::arg("sample"), + py::arg("event") /* Argument(s) */ + ); + } + const double *ReturnKinematicParameterByReference(double, int, int) override { + PYBIND11_OVERRIDE_PURE_NAME( + const double *, /* Return type */ + samplePDFFDBase, /* Parent class */ + "get_event_kinematic_value_reference",/* python name*/ + ReturnKinematicParameterByReference, /* Name of function in C++ (must match Python name) */ + py::arg("variable"), + py::arg("sample"), + py::arg("event") /* Argument(s) */ + ); + } + + std::vector ReturnKinematicParameterBinning(std::string) override { + PYBIND11_OVERRIDE_PURE_NAME( + std::vector, /* Return type */ + samplePDFFDBase, /* Parent class */ + "get_event_kinematic_binning",/* python name*/ + ReturnKinematicParameterBinning, /* Name of function in C++ (must match Python name) */ + py::arg("variable") /* Argument(s) */ + ); + } + + void fill2DHist() override { + PYBIND11_OVERRIDE_PURE( + void, /* Return type */ + samplePDFBase, /* Parent class */ + fill2DHist /* Name of function in C++ (must match Python name) */ + /* Argument(s) */ + ); + } +}; + void initSamplePDF(py::module &m){ auto m_sample_pdf = m.def_submodule("sample_pdf"); m_sample_pdf.doc() = "This module deals with sampling from the posterior density function of your particular experimental model at different points, given your data. \ - In order to do this, you will generally need to create a SamplePDF object derived from :py:class:`pyMaCh3.fitter.SamplePDFBase` for each sample of events for your experiment. \ + In order to do this, you will generally need to create a SamplePDF object derived from :py:class:`pyMaCh3.fitter.SamplePDFFDBase` for each sample of events for your experiment. \ For some more details on this you can see [the wiki page](https://github.com/mach3-software/MaCh3/wiki/04.-Making-a-samplePDF-experiment-class) on this. The code examples there are written using c++ however the general ideas are the same. \ Happy sampling!"; - - py::class_(m_sample_pdf, "SamplePDFBase") + py::class_(m_sample_pdf, "_SamplePDFBase") .def(py::init()) .def( @@ -86,7 +171,51 @@ void initSamplePDF(py::module &m){ &samplePDFBase::fill2DHist, "Do the initial filling of the sample for 2d histogram. You will need to override this." ) + ; // End of samplePDFBase binding + + py::class_(m_sample_pdf, "SamplePDFFDBase") + .def(py::init()) - ; - + .def( + "set_xsec_cov", + &samplePDFFDBase::SetXsecCov, + "Set the cross section covariance matrix object." + ) + + .def( + "set_osc_cov", + &samplePDFFDBase::SetOscCov, + "Set the oscillation parameter covariance matrix object." + ) + + ; + + /* Not sure if this will be needed in future versions of MaCh3 so leaving commented for now + py::class_(m_sample_pdf, "MCstruct") + .def(py::init()) + + // Because a lot of the variables in fdmc_base use c style arrays, + // we need to provide some setter functions to be able to set them using more + // "pythony" objects, e.g. lists and numpy arrays + .def( + "set_event_variable_values", + [](fdmc_base &self, int dim, py::array_t &array) + { + py::buffer_info bufInfo = array.request(); + + if ( dim > 2 ) + throw MaCh3Exception(__FILE__, __LINE__, "Currently only dimensions of 1 or 2 are supported sorry :("); + + if ( bufInfo.ndim != 1 ) + throw MaCh3Exception(__FILE__, __LINE__, "Number of dimensions in parameter array must be one if setting only one of the event variable arrays!"); + + if( dim ==1 ) + self.x_var = array.data(); + + else if ( dim == 2) + self.y_var = array.data(); + } + ) + ; + */ } \ No newline at end of file From dd38b2cef3e2a778dc03e3559d267d08488b1dc7 Mon Sep 17 00:00:00 2001 From: Ewan Date: Tue, 15 Oct 2024 07:40:15 -0700 Subject: [PATCH 15/24] some more functionality to make life a bit easier --- python/covariance.cpp | 7 +++++++ python/manager.cpp | 6 ++++++ python/samplePDF.cpp | 29 +++++++++++++++++++++++++++++ python/splines.cpp | 9 --------- samplePDF/samplePDFBase.h | 5 +++-- 5 files changed, 45 insertions(+), 11 deletions(-) diff --git a/python/covariance.cpp b/python/covariance.cpp index fc57af4ec..cf429b86d 100644 --- a/python/covariance.cpp +++ b/python/covariance.cpp @@ -209,6 +209,13 @@ void initCovariance(py::module &m){ :param index: The index of the spline parameter", py::arg("index") ) + + .def( + "get_nominal_par_values", + &covarianceXsec::getNominalArray, + "Get the nominal values of all the parameters as a list. " + ) + ; // End of CovarianceXsec binding py::class_(m_covariance, "CovarianceOsc") diff --git a/python/manager.cpp b/python/manager.cpp index 4bee35109..1751d6f0c 100644 --- a/python/manager.cpp +++ b/python/manager.cpp @@ -93,6 +93,12 @@ void initManager(py::module &m){ &manager::raw, "Get the raw yaml config." ) + + .def( + "get_test_stat", + &manager::GetMCStatLLH, + "Get the test statistic that was specified in the config file." + ) ; } diff --git a/python/samplePDF.cpp b/python/samplePDF.cpp index 8c3fee304..f4aa1eaab 100644 --- a/python/samplePDF.cpp +++ b/python/samplePDF.cpp @@ -145,6 +145,15 @@ void initSamplePDF(py::module &m){ For some more details on this you can see [the wiki page](https://github.com/mach3-software/MaCh3/wiki/04.-Making-a-samplePDF-experiment-class) on this. The code examples there are written using c++ however the general ideas are the same. \ Happy sampling!"; + // Bind the systematic type enum that lets us set different types of systematics + py::enum_(m_sample_pdf, "TestStatistic") + .value("Poisson", TestStatistic::kPoisson) + .value("Barlow_Beeston", TestStatistic::kBarlowBeeston) + .value("Ice_Cube", TestStatistic::kIceCube) + .value("Pearson", TestStatistic::kPearson) + .value("Dembinski_Abdelmottele", TestStatistic::kDembinskiAbdelmottele) + .value("N_Test_Statistics", TestStatistic::kNTestStatistics); + py::class_(m_sample_pdf, "_SamplePDFBase") .def(py::init()) @@ -171,6 +180,26 @@ void initSamplePDF(py::module &m){ &samplePDFBase::fill2DHist, "Do the initial filling of the sample for 2d histogram. You will need to override this." ) + + .def( + "set_test_stat", + &samplePDFBase::SetTestStatistic, + "Set the test statistic that should be used when calculating likelihoods. \n\ + :param test_stat: The new test statistic to use", + py::arg("test_stat") + ) + + .def( + "get_bin_LLH", + py::overload_cast(&samplePDFBase::getTestStatLLH), + "Get the LLH for a bin by comparing the data and MC. The result depends on having previously set the test statistic using :py:method:`pyMaCh3.sample_pdf.SamplePDFFDBase.set_test_stat` \n\ + :param data: The data content of the bin. \n\ + :param mc: The mc content of the bin \n\ + :param w2: The Sum(w_{i}^2) (sum of weights squared) in the bin, which is sigma^2_{MC stats}", + py::arg("data"), + py::arg("mc"), + py::arg("w2") + ) ; // End of samplePDFBase binding py::class_(m_sample_pdf, "SamplePDFFDBase") diff --git a/python/splines.cpp b/python/splines.cpp index fd3d47f2c..f103aeeba 100644 --- a/python/splines.cpp +++ b/python/splines.cpp @@ -118,16 +118,7 @@ void initSplines(py::module &m){ } else { - std::cout << "CALLING TSPline3 Constructor with " << length << " knots!" << std::endl; - for ( int i = 0; i < length; i++) - { - std:: cout << "{" << xVals[i] << ", " << yVals[i] << "} "; - } - std::cout << std::endl; - TSpline3 *splineTmp = new TSpline3( "spline_tmp", xVals.data(), yVals.data(), length ); - - std::cout << "Spline constructed successfully!" << std::endl << std::endl; return new TSpline3_red(splineTmp, interpType); } } diff --git a/samplePDF/samplePDFBase.h b/samplePDF/samplePDFBase.h index 3030ff469..1f2a131ea 100644 --- a/samplePDF/samplePDFBase.h +++ b/samplePDF/samplePDFBase.h @@ -81,8 +81,9 @@ class samplePDFBase /// @param mc is mc /// @param w2 is is Sum(w_{i}^2) (sum of weights squared), which is sigma^2_{MC stats} double getTestStatLLH(const double data, const double mc, const double w2); - // Provide a setter for the test-statistic - //void SetTestStatistic(TestStatistic test_stat); + /// @brief Set the test statistic to be used when calculating the binned likelihoods + /// @param testStat The test statistic to use. + inline void SetTestStatistic(TestStatistic testStat){ fTestStatistic = testStat; } virtual void fill1DHist()=0; virtual void fill2DHist()=0; From 66e73a97603c42773af746ebec455f15d4df933f Mon Sep 17 00:00:00 2001 From: Ewan Date: Wed, 16 Oct 2024 04:54:51 -0700 Subject: [PATCH 16/24] bind the non-default samplePDFFDBase constructor --- python/samplePDF.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/python/samplePDF.cpp b/python/samplePDF.cpp index f4aa1eaab..402804f7b 100644 --- a/python/samplePDF.cpp +++ b/python/samplePDF.cpp @@ -203,7 +203,17 @@ void initSamplePDF(py::module &m){ ; // End of samplePDFBase binding py::class_(m_sample_pdf, "SamplePDFFDBase") - .def(py::init()) + .def( + py::init(), + "This should never be called directly as samplePDFFDBase is an abstract base class. \n\ + However when creating a derived class, in the __init__() method, you should call the parent constructor i.e. this one by doing:: \n\ + \n\ + \tsuper(, self).__init__() \n\ + \n ", + py::arg("pot"), + py::arg("mc_version"), + py::arg("xsec_cov") + ) .def( "set_xsec_cov", From 1537058e58279b958a6068357b4fbed7df41daeb Mon Sep 17 00:00:00 2001 From: Ewan Date: Wed, 16 Oct 2024 06:38:20 -0700 Subject: [PATCH 17/24] remove binding for c++ function that was removed --- python/fitter.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/python/fitter.cpp b/python/fitter.cpp index 269dac3f0..c0e94fb62 100644 --- a/python/fitter.cpp +++ b/python/fitter.cpp @@ -118,16 +118,6 @@ void initFitter(py::module &m){ py::arg("oscf") ) - .def( - "add_osc_handler", - py::overload_cast(&FitterBase::addOscHandler), - " Adds an oscillation handler for covariance objects. \n" - " :param osca: A pointer to a covarianceOsc object for the first oscillation. \n" - " :param oscb: A pointer to a covarianceOsc object for the second oscillation. \n", - py::arg("osca"), - py::arg("oscb") - ) - ; // End of FitterBase class binding From 88669d2fe47836301b851568a1793132709a4cc6 Mon Sep 17 00:00:00 2001 From: Ewan Date: Wed, 16 Oct 2024 06:46:56 -0700 Subject: [PATCH 18/24] move some description stuff to actual documentation rather than just a docstring --- Doc/sphinx/source/sample-pdf.rst | 8 ++++++++ python/samplePDF.cpp | 5 +---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Doc/sphinx/source/sample-pdf.rst b/Doc/sphinx/source/sample-pdf.rst index 84451b779..ad5271e85 100644 --- a/Doc/sphinx/source/sample-pdf.rst +++ b/Doc/sphinx/source/sample-pdf.rst @@ -1,6 +1,14 @@ Sample PDF ========== +This module deals with sampling from the posterior density function of your +particular experimental model at different points, given your data. + +In order to do this, you will generally need to create a SamplePDF object derived from :py:class:`pyMaCh3.fitter.SamplePDFFDBase` +for each sample of events for your experiment. For some more details on this you can see [the wiki page](https://github.com/mach3-software/MaCh3/wiki/04.-Making-a-samplePDF-experiment-class) on this. +The code examples there are written using c++ however the general ideas are the same. +Happy sampling! + .. automodapi:: pyMaCh3.sample_pdf :members: :undoc-members: diff --git a/python/samplePDF.cpp b/python/samplePDF.cpp index 402804f7b..93c64a929 100644 --- a/python/samplePDF.cpp +++ b/python/samplePDF.cpp @@ -140,10 +140,7 @@ void initSamplePDF(py::module &m){ auto m_sample_pdf = m.def_submodule("sample_pdf"); m_sample_pdf.doc() = - "This module deals with sampling from the posterior density function of your particular experimental model at different points, given your data. \ - In order to do this, you will generally need to create a SamplePDF object derived from :py:class:`pyMaCh3.fitter.SamplePDFFDBase` for each sample of events for your experiment. \ - For some more details on this you can see [the wiki page](https://github.com/mach3-software/MaCh3/wiki/04.-Making-a-samplePDF-experiment-class) on this. The code examples there are written using c++ however the general ideas are the same. \ - Happy sampling!"; + "This is a Python binding of MaCh3s C++ based samplePDF library."; // Bind the systematic type enum that lets us set different types of systematics py::enum_(m_sample_pdf, "TestStatistic") From 2b519d4c2b5c5d1f23b54885ef07f1fb5761aa29 Mon Sep 17 00:00:00 2001 From: Ewan Date: Fri, 18 Oct 2024 05:23:38 -0700 Subject: [PATCH 19/24] typo --- python/covariance.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/covariance.cpp b/python/covariance.cpp index cf429b86d..9f5cfd273 100644 --- a/python/covariance.cpp +++ b/python/covariance.cpp @@ -189,7 +189,7 @@ void initCovariance(py::module &m){ .def( "get_par_type", &covarianceXsec::GetParamType, - "Get what type of systematic this parameters is (see :py:enum:`pyMaCh3.covarianc.SystematicType` for possible types). \n\ + "Get what type of systematic this parameters is (see :py:enum:`pyMaCh3.covariance.SystematicType` for possible types). \n\ :param index: The global index of the parameter", py::arg("index") ) From 74eaed23594383f5970a94bdde26cc8d533db32f Mon Sep 17 00:00:00 2001 From: Ewan Date: Fri, 18 Oct 2024 07:50:55 -0700 Subject: [PATCH 20/24] fix bindings for samplePDF to be up to date with new interface --- python/samplePDF.cpp | 80 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 72 insertions(+), 8 deletions(-) diff --git a/python/samplePDF.cpp b/python/samplePDF.cpp index 93c64a929..f6a2d966c 100644 --- a/python/samplePDF.cpp +++ b/python/samplePDF.cpp @@ -70,6 +70,70 @@ class PySamplePDFFDBase : public samplePDFFDBase { ); } + /* Trampoline (need one for each virtual function) */ + void SetupSplines() override { + PYBIND11_OVERRIDE_PURE_NAME( + void, /* Return type */ + samplePDFFDBase, /* Parent class */ + "setup_splines", /*python name*/ + SetupSplines, /* Name of function in C++ */ + /* Argument(s) */ + ); + } + + /* Trampoline (need one for each virtual function) */ + void Init() override { + PYBIND11_OVERRIDE_PURE_NAME( + void, /* Return type */ + samplePDFFDBase, /* Parent class */ + "init", /*python name*/ + Init, /* Name of function in C++ */ + /* Argument(s) */ + ); + } + + /* Trampoline (need one for each virtual function) */ + int setupExperimentMC(int) override { + PYBIND11_OVERRIDE_PURE_NAME( + int, /* Return type */ + samplePDFFDBase, /* Parent class */ + "setup_experiment_MC", /*python name*/ + setupExperimentMC, /* Name of function in C++ */ + py::arg("sample_id") /* Argument(s) */ + ); + } + + /* Trampoline (need one for each virtual function) */ + void setupFDMC(int) override { + PYBIND11_OVERRIDE_PURE_NAME( + void, /* Return type */ + samplePDFFDBase, /* Parent class */ + "setup_FD_MC", /*python name*/ + setupFDMC, /* Name of function in C++ */ + py::arg("sample_id") /* Argument(s) */ + ); + } + + int ReturnKinematicParameterFromString(std::string) override { + PYBIND11_OVERRIDE_PURE_NAME( + int, /* Return type */ + samplePDFFDBase, /* Parent class */ + "get_kinematic_by_name", /* python name*/ + ReturnKinematicParameterFromString, /* Name of function in C++ (must match Python name) */ + py::arg("variable_name") + ); + } + + std::string ReturnStringFromKinematicParameter(int) override { + PYBIND11_OVERRIDE_PURE_NAME( + std::string, /* Return type */ + samplePDFFDBase, /* Parent class */ + "get_kinematic_name", /* python name*/ + ReturnStringFromKinematicParameter, /* Name of function in C++ (must match Python name) */ + py::arg("variable_id") + ); + } + double ReturnKinematicParameter(std::string, int, int) override { PYBIND11_OVERRIDE_PURE_NAME( double, /* Return type */ @@ -81,6 +145,7 @@ class PySamplePDFFDBase : public samplePDFFDBase { py::arg("event") /* Argument(s) */ ); } + double ReturnKinematicParameter(double, int, int) override { PYBIND11_OVERRIDE_PURE_NAME( double, /* Return type */ @@ -93,23 +158,23 @@ class PySamplePDFFDBase : public samplePDFFDBase { ); } - const double *ReturnKinematicParameterByReference(std::string, int, int) override { + const double *GetPointerToKinematicParameter(std::string, int, int) override { PYBIND11_OVERRIDE_PURE_NAME( const double *, /* Return type */ samplePDFFDBase, /* Parent class */ "get_event_kinematic_value_reference",/* python name*/ - ReturnKinematicParameterByReference, /* Name of function in C++ (must match Python name) */ + GetPointerToKinematicParameter, /* Name of function in C++ (must match Python name) */ py::arg("variable"), py::arg("sample"), py::arg("event") /* Argument(s) */ ); } - const double *ReturnKinematicParameterByReference(double, int, int) override { + const double *GetPointerToKinematicParameter(double, int, int) override { PYBIND11_OVERRIDE_PURE_NAME( const double *, /* Return type */ samplePDFFDBase, /* Parent class */ "get_event_kinematic_value_reference",/* python name*/ - ReturnKinematicParameterByReference, /* Name of function in C++ (must match Python name) */ + GetPointerToKinematicParameter, /* Name of function in C++ (must match Python name) */ py::arg("variable"), py::arg("sample"), py::arg("event") /* Argument(s) */ @@ -151,7 +216,7 @@ void initSamplePDF(py::module &m){ .value("Dembinski_Abdelmottele", TestStatistic::kDembinskiAbdelmottele) .value("N_Test_Statistics", TestStatistic::kNTestStatistics); - py::class_(m_sample_pdf, "_SamplePDFBase") + py::class_(m_sample_pdf, "SamplePDFBase") .def(py::init()) .def( @@ -201,13 +266,12 @@ void initSamplePDF(py::module &m){ py::class_(m_sample_pdf, "SamplePDFFDBase") .def( - py::init(), + py::init(), "This should never be called directly as samplePDFFDBase is an abstract base class. \n\ However when creating a derived class, in the __init__() method, you should call the parent constructor i.e. this one by doing:: \n\ \n\ - \tsuper(, self).__init__() \n\ + \tsuper(, self).__init__(*args) \n\ \n ", - py::arg("pot"), py::arg("mc_version"), py::arg("xsec_cov") ) From cdc560eac48fb40ee31ac9f6cef0e9ad26d278e6 Mon Sep 17 00:00:00 2001 From: Ewan Date: Fri, 18 Oct 2024 09:10:10 -0700 Subject: [PATCH 21/24] D O C U M E N T A T I O N --- Doc/sphinx/source/manager.rst | 2 +- Doc/sphinx/source/plotting.rst | 2 +- Doc/sphinx/source/pyMaCh3.rst | 1 + Doc/sphinx/source/sample-pdf.rst | 2 +- Doc/sphinx/source/splines.rst | 68 ++++++++++++++++++++++++++++++++ 5 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 Doc/sphinx/source/splines.rst diff --git a/Doc/sphinx/source/manager.rst b/Doc/sphinx/source/manager.rst index 54bebfea0..5f62a08ab 100644 --- a/Doc/sphinx/source/manager.rst +++ b/Doc/sphinx/source/manager.rst @@ -2,7 +2,7 @@ Manager ======= This module handles the high level stuff like config options and YAML stuff. The main class is the :py:class:`pyMaCh3.manager.Manager` class. \ -You can read more about the manager and config files on [the wiki page](https://github.com/mach3-software/MaCh3/wiki/01.-Manager-and-config-handling). \ +You can read more about the manager and config files on `the wiki page `_. \ YAML stuff works essentially the same as in the c++ version but with some caveats. The main difference is that in the python version, the way that you access the actual data of a yaml node is different due to the way the python binding of the c++ code works. diff --git a/Doc/sphinx/source/plotting.rst b/Doc/sphinx/source/plotting.rst index 4dc5cd414..c7293bf97 100644 --- a/Doc/sphinx/source/plotting.rst +++ b/Doc/sphinx/source/plotting.rst @@ -1,7 +1,7 @@ plotting ======== -The plotting module can be used to make beautiful plots. See the [plotting wiki page](https://github.com/mach3-software/MaCh3/wiki/15.-Plotting) for information on how to configure the plotting library to work with your MaCh3 output files and other non-MaCh3 based fitters so you can compare results. +The plotting module can be used to make beautiful plots. See the `plotting wiki page `_ for information on how to configure the plotting library to work with your MaCh3 output files and other non-MaCh3 based fitters so you can compare results. The main class to worry about is :py:class:`pyMaCh3.plotting.PlottingManager` which provides the high level functionality and gives you access to everything else you should need. diff --git a/Doc/sphinx/source/pyMaCh3.rst b/Doc/sphinx/source/pyMaCh3.rst index 2b2dbbd5c..75ebd54d1 100644 --- a/Doc/sphinx/source/pyMaCh3.rst +++ b/Doc/sphinx/source/pyMaCh3.rst @@ -6,6 +6,7 @@ pyMaCh3 manager.rst sample-pdf.rst + splines.rst fitter.rst covariance.rst plotting.rst \ No newline at end of file diff --git a/Doc/sphinx/source/sample-pdf.rst b/Doc/sphinx/source/sample-pdf.rst index ad5271e85..079a5b576 100644 --- a/Doc/sphinx/source/sample-pdf.rst +++ b/Doc/sphinx/source/sample-pdf.rst @@ -5,7 +5,7 @@ This module deals with sampling from the posterior density function of your particular experimental model at different points, given your data. In order to do this, you will generally need to create a SamplePDF object derived from :py:class:`pyMaCh3.fitter.SamplePDFFDBase` -for each sample of events for your experiment. For some more details on this you can see [the wiki page](https://github.com/mach3-software/MaCh3/wiki/04.-Making-a-samplePDF-experiment-class) on this. +for each sample of events for your experiment. For some more details on this you can see `the wiki page `_ on this. The code examples there are written using c++ however the general ideas are the same. Happy sampling! diff --git a/Doc/sphinx/source/splines.rst b/Doc/sphinx/source/splines.rst new file mode 100644 index 000000000..6c25b0cbe --- /dev/null +++ b/Doc/sphinx/source/splines.rst @@ -0,0 +1,68 @@ +Splines +======= + +This module provides the utility for dealing with spline parameters. For some background reading se the `Splines wiki page `_. + +The main class which represents individual spline functions is :py:class:`pyMaCh3.splines.ResponseFunction`. +This is an abstract representation which covers multiple different types of interpolation, where the type of interpolation is specified at the time of construction. +The available interpolation types are defined by :any:`pyMaCh3.splines.InterpolationType`. Here are some examples + +.. image:: spline-examples.png + :width: 400 + :alt: Examples of linear, TSpline3 and monotonic interpolation + +To construct a ResponseFunction you must specify the x and y positions of the knots, and the interpolation type like in the following example:: + + from pyMaCh3 import splines + + TSpline3_response_1 = splines.ResponseFunction([0.0, 1.0, 2.0], [1.0, 0.5, 2.0], splines.InterpolationType.Cubic_TSpline3) + linear_response_1 = splines.ResponseFunction([10.0, 11.0, 12.0], [6.0, 0.0, 0.5], splines.InterpolationType.Linear) + + TSpline3_response_2 = splines.ResponseFunction([0.0, 1.0, 2.0], [2.0, 3.0, 0.0], splines.InterpolationType.Cubic_TSpline3) + linear_response_2 = splines.ResponseFunction([10.0, 11.0, 12.0], [3.0, 0.0, 4.5], splines.InterpolationType.Linear) + +Another important part of this module is the :any:`pyMaCh3.splines.EventSplineMonolith` class which allows you to easily and speedily deal with event-by-event splines in your analysis. +To build this you first need to construct a response function for each event-by-event spline parameter for each of your events as in the example above. + +Let's take those example responses and build a simple EventSplineMonolith:: + + monolith = splines.EventSplineMonolith([[TSpline3_response_1, linear_response_1], [TSpline3_response_2, linear_response_2]]) + +This will create an EventSplineMonolith which can deal with the reweighting of two events with two spline parameters. +We now need to be able to set the values of the parameters so that we can calculate event weights. +This is done using the :py:func:`pyMaCh3.splines.EventSplineMonolith.set_param_value_array` function. +This allows us to bind a numpy array to our EventSplineMonolith, whose values we can change, and this will set the values of the parameters inside of the monolith. +This works as follows:: + + # we need to keep track of how many parameters we have + # in our case this is two + n_parameters = 2 + + # we initialise the array, the initial values don't matter too much so we'll make them zero + param_array = np.zeros((n_parameters,)) + + # now we bind this array to the monolith we made above + monolith.set_param_value_array(param_array) + + # now changes made in the param_array will be propagated to the monolith + param_array[0] = 0.5 + param_array[1] = 11.042 + +.. warning:: + Once your array has been bound to your spline monolith that you keep it safe and don't overwrite it by doing something like:: + + param_array = np.array([0.5, 11.0]) + + as this will mean the original array (the one that is bound to your monolith will be lost forever and you will no longer be able to set parameter values!). + You need to set the values using indexes as above, or if you want to set them all at once you use slicing like:: + + param_array[...] = [0.5, 11.0] + + This may seem a bit faffy but it is intended to avoid unneccessary copying from python to c++ so we can keep things nice and speedy. + +Happy splining! + +.. automodapi:: pyMaCh3.splines + :members: + :undoc-members: + :show-inheritance: \ No newline at end of file From 78e156bac956f23bd7a491b50a38186ecc14a1cb Mon Sep 17 00:00:00 2001 From: Ewan Date: Mon, 21 Oct 2024 02:02:22 -0700 Subject: [PATCH 22/24] more documentation improvements --- Doc/sphinx/requirements.txt | 3 ++- Doc/sphinx/source/conf.py | 3 ++- Doc/sphinx/source/splines.rst | 4 ++-- python/covariance.cpp | 2 +- python/fitter.cpp | 8 ++++---- python/samplePDF.cpp | 19 ++++++++++--------- python/splines.cpp | 12 ++++++------ 7 files changed, 27 insertions(+), 24 deletions(-) diff --git a/Doc/sphinx/requirements.txt b/Doc/sphinx/requirements.txt index 927b6b090..febeb379a 100644 --- a/Doc/sphinx/requirements.txt +++ b/Doc/sphinx/requirements.txt @@ -1,4 +1,5 @@ sphinx sphinx-rtd-theme sphinx-automodapi -sphinx-mdinclude \ No newline at end of file +sphinx-mdinclude +enum-tools[sphinx] \ No newline at end of file diff --git a/Doc/sphinx/source/conf.py b/Doc/sphinx/source/conf.py index 2fb7b9ca6..d4feefa86 100644 --- a/Doc/sphinx/source/conf.py +++ b/Doc/sphinx/source/conf.py @@ -17,7 +17,8 @@ "sphinx.ext.autosummary", "sphinx.ext.napoleon", "sphinx_automodapi.automodapi", - 'sphinx_mdinclude' + "sphinx_mdinclude", + "enum_tools.autoenum" ] templates_path = ['_templates'] diff --git a/Doc/sphinx/source/splines.rst b/Doc/sphinx/source/splines.rst index 6c25b0cbe..35e5b5a32 100644 --- a/Doc/sphinx/source/splines.rst +++ b/Doc/sphinx/source/splines.rst @@ -5,7 +5,7 @@ This module provides the utility for dealing with spline parameters. For some ba The main class which represents individual spline functions is :py:class:`pyMaCh3.splines.ResponseFunction`. This is an abstract representation which covers multiple different types of interpolation, where the type of interpolation is specified at the time of construction. -The available interpolation types are defined by :any:`pyMaCh3.splines.InterpolationType`. Here are some examples +The available interpolation types are defined by :py:class:`pyMaCh3.splines.InterpolationType`. Here are some examples .. image:: spline-examples.png :width: 400 @@ -21,7 +21,7 @@ To construct a ResponseFunction you must specify the x and y positions of the kn TSpline3_response_2 = splines.ResponseFunction([0.0, 1.0, 2.0], [2.0, 3.0, 0.0], splines.InterpolationType.Cubic_TSpline3) linear_response_2 = splines.ResponseFunction([10.0, 11.0, 12.0], [3.0, 0.0, 4.5], splines.InterpolationType.Linear) -Another important part of this module is the :any:`pyMaCh3.splines.EventSplineMonolith` class which allows you to easily and speedily deal with event-by-event splines in your analysis. +Another important part of this module is the :py:class:`pyMaCh3.splines.EventSplineMonolith` class which allows you to easily and speedily deal with event-by-event splines in your analysis. To build this you first need to construct a response function for each event-by-event spline parameter for each of your events as in the example above. Let's take those example responses and build a simple EventSplineMonolith:: diff --git a/python/covariance.cpp b/python/covariance.cpp index 9f5cfd273..7668ae9f7 100644 --- a/python/covariance.cpp +++ b/python/covariance.cpp @@ -189,7 +189,7 @@ void initCovariance(py::module &m){ .def( "get_par_type", &covarianceXsec::GetParamType, - "Get what type of systematic this parameters is (see :py:enum:`pyMaCh3.covariance.SystematicType` for possible types). \n\ + "Get what type of systematic this parameters is (see :py:class:`pyMaCh3.covariance.SystematicType` for possible types). \n\ :param index: The global index of the parameter", py::arg("index") ) diff --git a/python/fitter.cpp b/python/fitter.cpp index 76c0a2040..91d31796e 100644 --- a/python/fitter.cpp +++ b/python/fitter.cpp @@ -124,16 +124,16 @@ void initFitter(py::module &m){ "add_syst_object", &FitterBase::addSystObj, " This function adds a Covariance object to the analysis framework. The Covariance object will be utilized in fitting procedures or likelihood scans. \n" - " :param cov: A pointer to a Covariance object derived from covarianceBase. \n", + " :param cov: A Covariance object derived from covarianceBase. ", py::arg("cov") ) .def( "add_osc_handler", py::overload_cast(&FitterBase::addOscHandler), - " Adds an oscillation handler for covariance objects. \n" - " :param oscf: A pointer to a covarianceOsc object for forward oscillations. \n", - py::arg("oscf") + " Adds an oscillation handler for covariance objects. \n" + " :param oscf: An oscillation handler object for dealing with neutrino oscillation calculations. ", + py::arg("osc") ) ; // End of FitterBase class binding diff --git a/python/samplePDF.cpp b/python/samplePDF.cpp index f6a2d966c..9088c2884 100644 --- a/python/samplePDF.cpp +++ b/python/samplePDF.cpp @@ -19,33 +19,34 @@ class PySamplePDFBase : public samplePDFBase { PYBIND11_OVERRIDE_PURE( void, /* Return type */ samplePDFBase, /* Parent class */ - reweight, /* Name of function in C++ (must match Python name) */ - /* Argument(s) */ + reweight /* Name of function in C++ (must match Python name) */ ); } double GetLikelihood() override { - PYBIND11_OVERRIDE_PURE( + PYBIND11_OVERRIDE_PURE_NAME( double, /* Return type */ samplePDFBase, /* Parent class */ - GetLikelihood, /* Name of function in C++ (must match Python name) */ + "get_likelihood", /* Python name*/ + GetLikelihood /* Name of function in C++ (must match Python name) */ /* Argument(s) */ ); } void fill1DHist() override { - PYBIND11_OVERRIDE_PURE( + PYBIND11_OVERRIDE_PURE_NAME( void, /* Return type */ samplePDFBase, /* Parent class */ - fill1DHist, /* Name of function in C++ (must match Python name) */ - /* Argument(s) */ + "fill_1d_hist", /* Python Name */ + fill1DHist /* Name of function in C++ (must match Python name) */ ); } void fill2DHist() override { - PYBIND11_OVERRIDE_PURE( + PYBIND11_OVERRIDE_PURE_NAME( void, /* Return type */ samplePDFBase, /* Parent class */ + "fill_2d_hist",/* Python name*/ fill2DHist /* Name of function in C++ (must match Python name) */ /* Argument(s) */ ); @@ -254,7 +255,7 @@ void initSamplePDF(py::module &m){ .def( "get_bin_LLH", py::overload_cast(&samplePDFBase::getTestStatLLH), - "Get the LLH for a bin by comparing the data and MC. The result depends on having previously set the test statistic using :py:method:`pyMaCh3.sample_pdf.SamplePDFFDBase.set_test_stat` \n\ + "Get the LLH for a bin by comparing the data and MC. The result depends on having previously set the test statistic using :py:meth:`pyMaCh3.sample_pdf.SamplePDFFDBase.set_test_stat` \n\ :param data: The data content of the bin. \n\ :param mc: The mc content of the bin \n\ :param w2: The Sum(w_{i}^2) (sum of weights squared) in the bin, which is sigma^2_{MC stats}", diff --git a/python/splines.cpp b/python/splines.cpp index f103aeeba..e6706831d 100644 --- a/python/splines.cpp +++ b/python/splines.cpp @@ -74,12 +74,12 @@ void initSplines(py::module &m){ // Bind the interpolation type enum that lets us set different interpolation types for our splines py::enum_(m_splines, "InterpolationType") - .value("Linear", SplineInterpolation::kLinear) - .value("Linear_Func", SplineInterpolation::kLinearFunc) - .value("Cubic_TSpline3", SplineInterpolation::kTSpline3) - .value("Cubic_Monotonic", SplineInterpolation::kMonotonic) - .value("Cubic_Akima", SplineInterpolation::kAkima) - .value("N_Interpolation_Types", SplineInterpolation::kSplineInterpolations); + .value("Linear", SplineInterpolation::kLinear, "Linear interpolation between the knots") + .value("Linear_Func", SplineInterpolation::kLinearFunc, "Same as 'Linear'") + .value("Cubic_TSpline3", SplineInterpolation::kTSpline3, "Use same coefficients as `ROOT's TSpline3 `_ implementation") + .value("Cubic_Monotonic", SplineInterpolation::kMonotonic, "Coefficients are calculated such that the segments between knots are forced to be monotonic. The implementation we use is based on `this method `_ by Fritsch and Carlson.") + .value("Cubic_Akima", SplineInterpolation::kAkima, "The second derivative is not required to be continuous at the knots. This means that these splines are useful if the second derivative is rapidly varying. The implementation we used is based on `this paper `_ by Akima.") + .value("N_Interpolation_Types", SplineInterpolation::kSplineInterpolations, "This is only to be used when iterating and is not a valid interpolation type."); py::class_(m_splines, "SplineBase"); From 41c4011f11f7959e7d0729a7a8c1f19c733826e1 Mon Sep 17 00:00:00 2001 From: Ewan Date: Mon, 21 Oct 2024 03:48:53 -0700 Subject: [PATCH 23/24] add spline example plot to docs --- Doc/sphinx/source/spline-examples.png | Bin 0 -> 62355 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Doc/sphinx/source/spline-examples.png diff --git a/Doc/sphinx/source/spline-examples.png b/Doc/sphinx/source/spline-examples.png new file mode 100644 index 0000000000000000000000000000000000000000..0493668bebd07752a610aca6593b13811e6dc605 GIT binary patch literal 62355 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 Date: Mon, 21 Oct 2024 05:19:56 -0700 Subject: [PATCH 24/24] small tweaks to make work with low mem floats and use std::vector instead of c-style array --- python/splines.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/python/splines.cpp b/python/splines.cpp index e6706831d..d9bba95b9 100644 --- a/python/splines.cpp +++ b/python/splines.cpp @@ -108,13 +108,20 @@ void initSplines(py::module &m){ if (length == 1) { - _float_ *pars[3]; - pars[0] = new double(0.0); - pars[1] = new double(0.0); - pars[2] = new double(0.0); - return new TSpline3_red(xVals.data(), yVals.data(), 1, pars); + _float_ xKnot = xVals[0]; + _float_ yKnot = yVals[0]; - delete[] pars; + std::vector<_float_ *> pars; + pars.resize(3); + pars[0] = new _float_(0.0); + pars[1] = new _float_(0.0); + pars[2] = new _float_(0.0); + + return new TSpline3_red(&xKnot, &yKnot, 1, pars.data()); + + delete pars[0]; + delete pars[1]; + delete pars[2]; } else { @@ -148,7 +155,7 @@ void initSplines(py::module &m){ [](std::vector> &responseFns, const bool saveFlatTree) { std::vector respFnTypes; - for(int i = 0; i < responseFns[0].size(); i++) + for(uint i = 0; i < responseFns[0].size(); i++) { // ** WARNING ** // Right now I'm only pushing back TSpline3_reds as thats all thats supported right now