Skip to content

Python Binding

Ewan Miller edited this page Mar 10, 2026 · 1 revision

Python Binding {#PythonBinding}

MaCh3 provides python bindings for all of the public interface of the classes in the core code which are created using pybind11. This means that all functionality available in the c++ interface is also available in python.

Binding Experiment Code

Functionality is also provided to create a python module for experiment specific MaCh3 code. To do this you simply need to create a c++ file that defines the bindings, and create the python module using the setup_pyMaCh3 cmake function. Details on how to do this are given below.

Binding File

The first step is to include the pyMaCh3.h header in your binding file (which for this example we will call pyMaCh3Experiment.cpp).

#include python/pyMaCh3.h

The binding is created by the MaCh3PyBinder class. Experiments can override the initSamplesExperiment(), initParametersExperiment(), ... methods of this class to inject your own bindings into the samples, parameters, ... python modules as well as the initModulesExperiment() to add your own modules.

The rule of thumb here is that any functionality that you have added which does not exist in the MaCh3 base classes, will need to be bound here if you want to use it in python (Note that this also includes constructors for any of your derived classes). For the syntax and more details on how exactly the code to do this should be written, please refer to the pybind11 documentation on creating class bindings.

The absolute bare minimum working example would be to just bind the experiments sample handler class and its' constructors like so:

#include "SampleHandlerExperiment.h"
#include <memory>

namespace py = pybind11;

## inherit from the MaCh3PyBinder base class
class MaCh3ExperimentPyBinder : public MaCh3PyBinder {

  public:

    # override this method to insert bindings into the samples python submodule
    void initSamplesExperiment(py::module &m_samples) override {
      // SampleHandlerExperiment to the samples submodule
      py::class_<SampleHandlerExperiment, SampleHandlerFD, SampleHandlerBase>(m_samples, "SampleHandlerExperiment")
        // Constructor with 2 arguments (xsec covariance and osc covariance)
        .def(py::init([](
            ParameterHandlerGeneric* xsec_cov,
            OscillationHandler* osc_cov) 
        {
            std::shared_ptr<OscillationHandler> osc_ptr;
            if (osc_cov != nullptr) {
                osc_ptr = std::shared_ptr<OscillationHandler>(osc_cov, [](OscillationHandler*){});
            }
            return new SampleHandlerExperiment(xsec_cov, osc_ptr);
        }),
            "Create SampleHandlerExperiment with oscillation handler",
            py::arg("xsec_cov"),
            py::arg("osc_cov") = nullptr
        )
        ;
    }
};

This will give you access to the SampleHandlerExperiment class within python like so:

from pyMaCh3.samples import SampleHandlerExperiment

and it will have all of the functionality of the virtual interface described by the base sample handler.

The final step is to initialise the module using the MAKE_PYMACH3_MDULE() macro. You just need to pass your MaCh3 py binder class into this like so:

MAKE_PYMACH3_MDULE( MaCh3ExperimentPyBinder )

CMake

Once you have your binding file (for our example, pyMaCh3Experiment.cpp), you need to tell cmake to install the python module for your experiment. This is done by using the setup_pyMaCh3 function defined in <MaCh3 core path>/cmake/Modules/pyMaCh3.cmake by adding something like the following to your CMakeLists.txt:

include(${MaCh3_PREFIX}/cmake/Modules/pyMaCh3.cmake)

setup_pyMaCh3(
  INSTALL_DIR "NONE"
  BINDING_FILES pyMaCh3Experiment.cpp
  LINK_TARGETS 
    SamplesExperiment
)

The INSTALL_DIR argument is the location to install the python module to. If this is "NONE", it will be installed to the directory defined by the cmake variable CMAKE_INSTALL_PREFIX

BINDING_FILES is a list of files which define the binding (for our example this is just pyMaCh3Experiment.cpp)

LINK_TARGETS defines any experiment specific cmake targets which must be linked into our pyMaCh3 module. Note that there are some pitfalls here. One is that each of these targets should produce a lib.so which the python module will load using ctypes.cdll.LoadLibrary(). Another is that the order in which you provide these targets matters: if targetA depends on targetB, then targetB must come before targetA in the list i.e.

LINK_TARGETS
  targetB
  targetA

This is because this is the order that the python module will try to load the libraries and so if targetA is loaded first, it will not find the symbols defined in targetB and then panic.

There is also an additional argument EXTRA_MODULES which is a list of the names of any extra modules you defined in the initModulesExperiment() method. This is needed for the final module to be able to locate these submodules when calling import.

Clone this wiki locally