Using pybind11¶
The package pybind11 is provides an elegant way to wrap C++ code for
Python, including automatic conversions for numpy arrays and the C++
Eigen linear algebra library. Used with the cppimport package,
this provides a very nice work flow for integrating C++ and Python:
- Edit C++ code
- Run Python code
pip install pybind11
pip install cppimport
Clone the Eigen library - no installation is required as Eigen is a header only library.
hg clone https://bitbucket.org/eigen/eigen/
A first example of using pybind11¶
Create a new subdirectory - e.g. example1 and create the following 5
files in it:
funcs.hppfuncs.cppwrap.cppsetup.pytest_funcs.py
First write the C++ header and implementation files
In funcs.hpp
int add(int i, int j);
In funcs.cpp
int add(int i, int j) {
return i + j;
};
Next write the C++ wrapper code using pybind11 in wrap.cpp. The
arguments "i"_a=1, "j"_a=2 in the exported function definition tells
pybind11 to generate variables named i with default value 1 and
j with default value 2 for the add function.
#include <pybind11/pybind11.h>
#include "funcs.hpp"
namespace py = pybind11;
using namespace pybind11::literals;
PYBIND11_PLUGIN(wrap) {
py::module m("wrap", "pybind11 example plugin");
m.def("add", &add, "A function which adds two numbers",
"i"_a=1, "j"_a=2);
return m.ptr();
}
Finally, write the setup.py file to compile the extension module.
This is mostly boilerplate.
import os, sys
from distutils.core import setup, Extension
from distutils import sysconfig
cpp_args = ['-std=c++11', '-stdlib=libc++', '-mmacosx-version-min=10.7']
ext_modules = [
Extension(
'wrap',
['funcs.cpp', 'wrap.cpp'],
include_dirs=['pybind11/include'],
language='c++',
extra_compile_args = cpp_args,
),
]
setup(
name='wrap',
version='0.0.1',
author='Cliburn Chan',
author_email='cliburn.chan@duke.edu',
description='Example',
ext_modules=ext_modules,
)
Now build the extension module in the subdirectory with these files
python setup.py build_ext -i
And if you are successful, you should now see a new funcs.so
extension module. We can write a test_funcs.py file test the
extension module:
import wrap
def test_add():
assert(wrap.add(3, 4) == 7)
if __name__ == '__main__':
test_add()
And finally, running the test should not generate any error messages:
$ python test_funcs.py
Using cppimport¶
In the development stage, it can be distracting to have to repeatedly rebuild the extension module by running
python setup.py clean
python setup.py build_ext -i
every single time you modify the C++ code. The cppimport package
does this for you.
Create a new sub-directory exaample2 and copy the files
func.hpp, funcs.cpp and wrap.cpp from example1 over. For
the previous example, we just need to add some annotation (between
<% and %> delimiters) to the top of the wrap.cpp file
<%
cfg['compiler_args'] = ['-std=c++11', '-stdlib=libc++', '-mmacosx-version-min=10.7']
cfg['sources'] = ['funcs.cpp']
setup_pybind11(cfg)
%>
#include "funcs.hpp"
#include <pybind11/pybind11.h>
namespace py = pybind11;
PYBIND11_PLUGIN(wrap) {
py::module m("wrap", "pybind11 example plugin");
m.def("add", &add, "A function which adds two numbers");
return m.ptr();
}
and use cpppimport in the test_funcs.py file.
import cppimport
funcs = cppimport.imp("wrap")
def test_add():
assert(funcs.add(3, 4) == 7)
if __name__ == '__main__':
test_add()
You can now run
$ python test_funcs.py
without any need to manually build the extension module. Any updates
will be detected by cppimport and it will automatically trigger a
re-build.
Vectorizing functions for use with numpy arrays¶
Example showing how to vectorize a square function
<%
cfg['compiler_args'] = ['-std=c++11', '-stdlib=libc++', '-mmacosx-version-min=10.7']
setup_pybind11(cfg)
%>
#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>
namespace py = pybind11;
double square(double x) {
return x * x;
}
PYBIND11_PLUGIN(code) {
pybind11::module m("code", "auto-compiled c++ extension");
m.def("square", py::vectorize(square));
return m.ptr();
}
and the test file
import cppimport
import numpy as np
code = cppimport.imp("code")
if __name__ == '__main__':
xs = np.random.rand(10)
print(xs)
print(code.square(xs))
ys = range(10)
print(code.square(ys))
Using numpy arrays as function arguments and return values¶
Example showing how to pass numpy arrays in and out of functions.
These numpy array arguments can either be generic py:array or
typed py:array_t<double>. The properties of the numpy array can
be obtained by calling its request method. This returns a struct
of the following form:
struct buffer_info {
void *ptr;
size_t itemsize;
std::string format;
int ndim;
std::vector<size_t> shape;
std::vector<size_t> strides;
};
Here is C++ code for two functions - the function twice shows how to
change a passed in numpy array in-place using pointers; the function
sum shows how to sum the elements of a numpy array. By taking
advantage of the information in buffer_info, the code will work for
arbitrary n-d arrays.
<%
cfg['compiler_args'] = ['-std=c++11', '-stdlib=libc++', '-mmacosx-version-min=10.7']
setup_pybind11(cfg)
%>
#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>
namespace py = pybind11;
// Passing in an array of doubles
void twice(py::array_t<double> xs) {
py::buffer_info info = xs.request();
auto ptr = static_cast<double *>(info.ptr);
int n = 1;
for (auto r: info.shape) {
n *= r;
}
for (int i = 0; i <n; i++) {
*ptr++ *= 2;
}
}
// Passing in a generic array
double sum(py::array xs) {
py::buffer_info info = xs.request();
auto ptr = static_cast<double *>(info.ptr);
int n = 1;
for (auto r: info.shape) {
n *= r;
}
double s = 0.0;
for (int i = 0; i <n; i++) {
s += *ptr++;
}
return s;
}
PYBIND11_PLUGIN(code) {
pybind11::module m("code", "auto-compiled c++ extension");
m.def("sum", &sum);
m.def("twice", &twice);
return m.ptr();
}
and the test code
import cppimport
import numpy as np
code = cppimport.imp("code")
if __name__ == '__main__':
xs = np.arange(12).reshape(3,4).astype('float')
print(xs)
print("np :", xs.sum())
print("cpp:", code.sum(xs))
print()
code.twice(xs)
print(xs)
Using the C++ eigen library to calculate matrix inverse and determinant¶
Example showing how Eigen vectors and matrices can be passed in and
out of C++ functions. Note that Eigen arrays are automatically
converted to numpy arrays simply by including the pybind/eigen.h
header.
<%
cfg['compiler_args'] = ['-std=c++11', '-stdlib=libc++', '-mmacosx-version-min=10.7']
cfg['include_dirs'] = ['/Users/cliburn/hg/eigen']
setup_pybind11(cfg)
%>
#include <pybind11/pybind11.h>
#include <pybind11/eigen.h>
#include <Eigen/LU>
namespace py = pybind11;
Eigen::MatrixXd inv(Eigen::MatrixXd xs) {
return xs.inverse();
}
double det(Eigen::MatrixXd xs) {
return xs.determinant();
}
PYBIND11_PLUGIN(code) {
pybind11::module m("code", "auto-compiled c++ extension");
m.def("inv", &inv);
m.def("det", &det);
return m.ptr();
}
and test code
import cppimport
import numpy as np
code = cppimport.imp("code")
if __name__ == '__main__':
A = np.array([[1,2,1],
[2,1,0],
[-1,1,2]])
print(A)
print(code.det(A))
print(code.inv(A))