Skip to content

Commit

Permalink
Merge pull request #176 from mach3-software/feature_fitter_python_int…
Browse files Browse the repository at this point in the history
…erface

Python Interface for MaCh3
  • Loading branch information
ewanwm authored Oct 21, 2024
2 parents 22bd734 + 71bbf45 commit dce53c0
Show file tree
Hide file tree
Showing 25 changed files with 1,352 additions and 52 deletions.
18 changes: 3 additions & 15 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -210,24 +210,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)
Expand Down
3 changes: 2 additions & 1 deletion Doc/sphinx/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
sphinx
sphinx-rtd-theme
sphinx-automodapi
sphinx-mdinclude
sphinx-mdinclude
enum-tools[sphinx]
3 changes: 2 additions & 1 deletion Doc/sphinx/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"sphinx.ext.autosummary",
"sphinx.ext.napoleon",
"sphinx_automodapi.automodapi",
'sphinx_mdinclude'
"sphinx_mdinclude",
"enum_tools.autoenum"
]

templates_path = ['_templates']
Expand Down
10 changes: 10 additions & 0 deletions Doc/sphinx/source/covariance.rst
Original file line number Diff line number Diff line change
@@ -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:
10 changes: 10 additions & 0 deletions Doc/sphinx/source/fitter.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
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:
:show-inheritance:
51 changes: 51 additions & 0 deletions Doc/sphinx/source/manager.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
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<int>()
// for the string
std::string testStr = node['Str'].as<std::string>()
// for the integer array
... = node['IntArray'].as<std::vector<int>>()
// and for the string array
... = node['StrArray'].as<std::vector<std::string>>()

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:
:show-inheritance:
17 changes: 0 additions & 17 deletions Doc/sphinx/source/plotting.md

This file was deleted.

21 changes: 20 additions & 1 deletion Doc/sphinx/source/plotting.rst
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
5 changes: 5 additions & 0 deletions Doc/sphinx/source/pyMaCh3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,9 @@ pyMaCh3
.. toctree::
:maxdepth: 4

manager.rst
sample-pdf.rst
splines.rst
fitter.rst
covariance.rst
plotting.rst
15 changes: 15 additions & 0 deletions Doc/sphinx/source/sample-pdf.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
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:
:show-inheritance:
Binary file added Doc/sphinx/source/spline-examples.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
68 changes: 68 additions & 0 deletions Doc/sphinx/source/splines.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
Splines
=======

This module provides the utility for dealing with spline parameters. For some background reading se the `Splines wiki page <https://github.com/mach3-software/MaCh3/wiki/05.-Splines>`_.

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 :py:class:`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 :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::
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:
4 changes: 4 additions & 0 deletions covariance/covarianceBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,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<double> &getParPropVec() {return _fPropVal;}

//Some Getters
/// @brief Get total number of parameters
inline int GetNumParams() {return _fNumPar;}
Expand Down
3 changes: 3 additions & 0 deletions covariance/covarianceXsec.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<int> GetGlobalSystIndexFromDetID(const int DetID, const SystType Type);
Expand Down
10 changes: 0 additions & 10 deletions pyMaCh3.cpp

This file was deleted.

21 changes: 21 additions & 0 deletions python/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

################################# 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
fitter.cpp
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.
set_property( TARGET pyMaCh3 PROPERTY POSITION_INDEPENDENT_CODE ON )
target_link_libraries( pyMaCh3 PUBLIC MaCh3::All )
install( TARGETS pyMaCh3 DESTINATION pyMaCh3/)
endif()
Loading

0 comments on commit dce53c0

Please sign in to comment.