Let’s try a non-trivial example of a binding: Minuit2 (6.14.0 standalone edition).
Requirements
- Minuit2 6.14.0 standalone edition (included)
- Pybind11 (included)
- NumPy
- C++11 compatible compiler
- CMake 3
Expectations
- Be able to minimize a very simple function and get some parameters
Step 1: Get source
- Download Minuit2 source (provided in
minuit2src
) - Install Pybind11 or add as submodule (provided in
pybind11
)
Step 2: Plan interface
- You should know what the C++ looks like, and know what you want the Python to look like
- For now, let’s replicate the C++ experience
For example: a simple minimizer for $f(x) = x^2$ (should quickly find 0 as minimum):
- Define FCN
- Setup parameters
- Minimize
- Print result
Will use print out for illustration (instead of MnPrint::SetLevel
)
%%writefile SimpleFCN.h
#pragma once
#include <Minuit2/FCNBase.h>
#include <Minuit2/FunctionMinimum.h>
#include <Minuit2/MnPrint.h>
#include <Minuit2/MnMigrad.h>
using namespace ROOT::Minuit2;
class SimpleFCN : public FCNBase {
double Up() const override {return 0.5;}
double operator()(const std::vector<double> &v) const override {
std::cout << "val = " << v.at(0) << std::endl;
return v.at(0)*v.at(0);
}
};
%%writefile simpleminuit.cpp
#include "SimpleFCN.h"
int main() {
SimpleFCN fcn;
MnUserParameters upar;
upar.Add("x", 1., 0.1);
MnMigrad migrad(fcn, upar);
FunctionMinimum min = migrad();
std::cout << min << std::endl;
}
%%writefile CMakeLists.txt
cmake_minimum_required(VERSION 3.4)
project(Minuit2SimpleExamle LANGUAGES CXX)
add_subdirectory(minuit2src)
add_executable(simpleminuit simpleminuit.cpp SimpleFCN.h)
target_link_libraries(simpleminuit PRIVATE Minuit2::Minuit2)
Standard CMake configure and build (using Ninja instead of Make for speed)
!cmake -GNinja .
!cmake --build .
-- Configuring done
-- Generating done
-- Build files have been written to: pybindings_cc
[2/2] Linking CXX executable simpleminuit/Minuit.dir/simpleminuit.cpp.o
!./simpleminuit
val = 1
val = 1.001
val = 0.999
val = 1.0006
val = 0.999402
val = -8.23008e-11
val = 0.000345267
val = -0.000345267
val = -8.23008e-11
val = 0.000345267
val = -0.000345267
val = 6.90533e-05
val = -6.90535e-05
Minuit did successfully converge.
# of function calls: 13
minimum function Value: 6.773427082119e-21
minimum edm: 6.773427081817e-21
minimum internal state vector: LAVector parameters:
-8.230083281546e-11
minimum internal covariance matrix: LASymMatrix parameters:
1
# ext. || Name || type || Value || Error +/-
0 || x || free || -8.230083281546e-11 ||0.7071067811865
Step 3: Bind parts we need
- subclassable FCNBase
- MnUserParameters (constructor and
Add(string, double, double)
) - MnMigrad (constructor and operator())
- FunctionMinimum (cout)
Recommended structure of a Pybind11 program
main.cpp
- Builds module
- Avoids imports (fast compile)
include <pybind11/pybind11.h>
namespace py = pybind11;
void init_part1(py::module &);
void init_part2(py::module &);
PYBIND11_MODULE(mymodule, m) {
m.doc() = "Real code would never have such poor documentation...";
init_part1(m);
init_part2(m);
}
mkdir -p pyminuit2
%%writefile pyminuit2/pyminuit2.cpp
#include <pybind11/pybind11.h>
namespace py = pybind11;
void init_FCNBase(py::module &);
void init_MnUserParameters(py::module &);
void init_MnMigrad(py::module &);
void init_FunctionMinimum(py::module &);
PYBIND11_MODULE(minuit2, m) {
init_FCNBase(m);
init_MnUserParameters(m);
init_MnMigrad(m);
init_FunctionMinimum(m);
}
We will put all headers in a collective header (not a good idea to put all headers in one file unless you are trying to show files one per slide like I was).
%%writefile pyminuit2/PyHeader.h
#pragma once
#include <pybind11/pybind11.h>
#include <pybind11/functional.h>
#include <pybind11/numpy.h>
#include <pybind11/stl.h>
#include <Minuit2/FCNBase.h>
#include <Minuit2/MnMigrad.h>
#include <Minuit2/MnApplication.h>
#include <Minuit2/MnUserParameters.h>
#include <Minuit2/FunctionMinimum.h>
namespace py = pybind11;
using namespace pybind11::literals;
using namespace ROOT::Minuit2;
Overloads
- Pure virtual methods cannot be instantiated in C++
- Have to provide “Trampoline class” to provide Python class
%%writefile pyminuit2/FCNBase.cpp
#include "PyHeader.h"
class PyFCNBase : public FCNBase {
public:
using FCNBase::FCNBase;
double operator()(const std::vector<double> &v) const override {
PYBIND11_OVERLOAD_PURE_NAME(
double, FCNBase, "__call__", operator(), v);}
double Up() const override {
PYBIND11_OVERLOAD_PURE(double, FCNBase, Up, );}
};
void init_FCNBase(py::module &m) {
py::class_<FCNBase, PyFCNBase>(m, "FCNBase")
.def(py::init<>())
.def("__call__", &FCNBase::operator())
.def("Up", &FCNBase::Up);
}
Overloaded function signatures:
- C++11 syntax:
(bool (MnUserParameters::*)(const std::string &, double)) &MnUserParameters::Add
- C++14 syntax:
py::overload_cast<const std::string &, double>(&MnUserParameters::Add)
%%writefile pyminuit2/MnUserParameters.cpp
#include "PyHeader.h"
void init_MnUserParameters(py::module &m) {
py::class_<MnUserParameters>(m, "MnUserParameters")
.def(py::init<>())
.def("Add", (bool (MnUserParameters::*)(const std::string &, double)) &MnUserParameters::Add)
.def("Add", (bool (MnUserParameters::*)(const std::string &, double, double)) &MnUserParameters::Add)
;
}
Adding default arguments (and named arguments)
- Using
""_a
literal, names and even defaults can be added
%%writefile pyminuit2/MnMigrad.cpp
#include "PyHeader.h"
void init_MnMigrad(py::module &m) {
py::class_<MnApplication>(m, "MnApplication")
.def("__call__",
&MnApplication::operator(),
"Minimize the function, returns a function minimum",
"maxfcn"_a = 0,
"tolerance"_a = 0.1);
py::class_<MnMigrad, MnApplication>(m, "MnMigrad")
.def(py::init<const FCNBase &, const MnUserParameters &, unsigned int>(),
"fcn"_a, "par"_a, "stra"_a = 1)
;
}
Lambda functions
- Pybind11 accepts lambda functions, as well
%%writefile pyminuit2/FunctionMinimum.cpp
#include "PyHeader.h"
#include <sstream>
#include <Minuit2/MnPrint.h>
void init_FunctionMinimum(py::module &m) {
py::class_<FunctionMinimum>(m, "FunctionMinimum")
.def("__str__", [](const FunctionMinimum &self) {
std::stringstream os;
os << self;
return os.str();
})
;
}
%%writefile CMakeLists.txt
cmake_minimum_required(VERSION 3.4)
project(Minuit2SimpleExamle LANGUAGES CXX)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
add_subdirectory(minuit2src)
add_executable(simpleminuit simpleminuit.cpp SimpleFCN.h)
target_link_libraries(simpleminuit PRIVATE Minuit2::Minuit2)
add_subdirectory(pybind11)
file(GLOB OUTPUT pyminuit2/*.cpp)
pybind11_add_module(minuit2 ${OUTPUT})
target_link_libraries(minuit2 PUBLIC Minuit2::Minuit2)
!cmake .
!cmake --build .
-- pybind11 v2.2.3
-- Configuring done
-- Generating done
-- Build files have been written to: pybindings_cc
[85/85] Linking CXX shared module minuit2.cpython-36m-x86_64-linux-gnu.so
Usage
We can now use our module! (Built in the current directory by CMake)
import sys
if "." not in sys.path:
sys.path.append(".")
import minuit2
class SimpleFCN(minuit2.FCNBase):
def Up(self):
return 0.5
def __call__(self, v):
print("val =", v[0])
return v[0] ** 2
fcn = SimpleFCN()
upar = minuit2.MnUserParameters()
upar.Add("x", 1.0, 0.1)
migrad = minuit2.MnMigrad(fcn, upar)
min = migrad()
val = 1.0
val = 1.001
val = 0.999
val = 1.0005980198587356
val = 0.9994019801412644
val = -8.230083281546285e-11
val = 0.00034526688527999595
val = -0.0003452670498816616
val = -8.230083281546285e-11
val = 0.00034526688527999595
val = -0.0003452670498816616
val = 6.905331121533294e-05
val = -6.905347581699857e-05
print(min)
Minuit did successfully converge.
# of function calls: 13
minimum function Value: 6.773427082119e-21
minimum edm: 6.773427081817e-21
minimum internal state vector: LAVector parameters:
-8.230083281546e-11
minimum internal covariance matrix: LASymMatrix parameters:
1
# ext. || Name || type || Value || Error +/-
0 || x || free || -8.230083281546e-11 ||0.7071067811865
Done
- See GooFit’s built in Minuit2 bindings for a more complete example
- Pybind11 bindings can talk to each other at the C level!
Bonus:
This is the setup.py file for the Miniut2 bindings. With this, you can use the standard Python tools to build! (but slower and more verbose than CMake)
%%writefile setup.py
from setuptools import setup, Extension
from setuptools.command.build_ext import build_ext
import sys
import setuptools
from pathlib import Path # Python 3 or Python 2 backport: pathlib2
import pybind11 # Real code should defer this import
sources = set(str(p) for p in Path('Minuit2-6.14.0-Source/src').glob('**/*.cxx'))
sources.remove('Minuit2-6.14.0-Source/src/TMinuit2TraceObject.cxx')
## Add your sources to `sources`
sources |= set(str(p) for p in Path('pyminuit2').glob('*.cpp'))
ext_modules = [
Extension(
'minuit2',
list(sources),
include_dirs=[
pybind11.get_include(False),
pybind11.get_include(True),
'Minuit2-6.14.0-Source/inc',
],
language='c++',
define_macros=[('WARNINGMSG', None),
('MATH_NO_PLUGIN_MANAGER', None),
('ROOT_Math_VecTypes', None)
],
),
]
class BuildExt(build_ext):
"""A custom build extension for adding compiler-specific options."""
c_opts = {
'msvc': ['/EHsc'],
'unix': [],
}
if sys.platform == 'darwin':
c_opts['unix'] += ['-stdlib=libc++', '-mmacosx-version-min=10.7']
def build_extensions(self):
ct = self.compiler.compiler_type
opts = self.c_opts.get(ct, [])
if ct == 'unix':
opts.append('-DVERSION_INFO="%s"' % self.distribution.get_version())
opts.append('-std=c++14')
opts.append('-fvisibility=hidden')
elif ct == 'msvc':
opts.append('/DVERSION_INFO=\\"%s\\"' % self.distribution.get_version())
for ext in self.extensions:
ext.extra_compile_args = opts
build_ext.build_extensions(self)
setup(
name='minuit2',
version='6.14.0',
author='Henry Schriener',
author_email='my@email.address',
url='https://github.com/GooFit/Minuit2',
description='A Pybind11 Minuit2 binding',
long_description='',
ext_modules=ext_modules,
install_requires=['pybind11>=2.2', 'numpy>=1.10'],
cmdclass={'build_ext': BuildExt},
zip_safe=False,
)
#!python setup.py build_ext