使用 pybind11 转换 NumPy 数组 to/from 自定义 C++ 矩阵-class
Cast NumPy array to/from custom C++ Matrix-class using pybind11
我正在尝试使用 pybind11
包装我的 C++ 代码。在 C++ 中,我有一个 class Matrix3D
充当 3-D 数组(即形状 [n,m,p]
)。它具有以下基本签名:
template <class T> class Matrix3D
{
public:
std::vector<T> data;
std::vector<size_t> shape;
std::vector<size_t> strides;
Matrix3D<T>();
Matrix3D<T>(std::vector<size_t>);
Matrix3D<T>(const Matrix3D<T>&);
T& operator() (int,int,int);
};
为了最小化包装代码,我想将此 class 直接转换为 NumPy 数组(副本没有问题)。例如,我想直接包装一个具有以下签名的函数:
Matrix3D<double> func ( const Matrix3D<double>& );
使用封装代码
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/numpy.h>
namespace py = pybind11;
PYBIND11_PLUGIN(example) {
py::module m("example", "Module description");
m.def("func", &func, "Function description" );
return m.ptr();
}
目前我有另一个接受和 returns py::array_t<double>
的函数。但是我想通过用一些模板替换它来避免为每个函数编写一个包装函数。
已为 Eigen
-库(用于数组和 (2-D) 矩阵)完成此操作。但是代码太复杂了,我无法从中导出自己的代码。另外,我真的只需要包装一个,简单,class。
我对pybind11不是很熟悉,但是看了这个问题就产生了兴趣。从 documentanion 看来,您必须自己编写 type caster。这显然是一个相当高级的主题,但似乎可以通过一些努力实现。
从文档中删除,这是用于转换 C++ 类型的转换器的 shell inty
:
namespace pybind11 { namespace detail {
template <> struct type_caster<inty> {
public:
PYBIND11_TYPE_CASTER(inty, _("inty"));
// Conversion part 1 (Python->C++)
bool load(handle src, bool);
//Conversion part 2 (C++ -> Python)
static handle cast(inty src, return_value_policy, handle);
};
}} // namespace pybind11::detail
看来你所要做的就是将inty
替换为Matrix3D<double>
并实现load()
和cast()
.
让我们看看他们是如何为 Eigen 做的(eigen.h, line 236 以后):
bool load(handle src, bool) {
auto buf = array_t<Scalar>::ensure(src);
if (!buf)
return false;
auto dims = buf.ndim();
if (dims < 1 || dims > 2)
return false;
auto fits = props::conformable(buf);
if (!fits)
return false; // Non-comformable vector/matrix types
value = Eigen::Map<const Type, 0, EigenDStride>(buf.data(), fits.rows, fits.cols, fits.stride);
return true;
}
这看起来并不难。首先,他们确保输入的类型为 array_t<Scalar>
(在您的情况下可能是 array_t<double>
)。然后他们检查尺寸和一些一致性(您可以跳过后者)。最后创建特征矩阵。由于复制不是问题,此时只需创建一个新的 Martix3D<double>
实例并用 numpy 数组中的数据填充它。
对于左值和常量的不同情况,cast()
函数有不同的实现。我想只做一个在新的 numpy 数组中创建数据副本的实现就足够了,如果这样的话。请参阅函数 eigen_array_cast()
如何将数组 return 作为 handle
return 类型。
我还没有测试过这些,这个过程可能比看起来要复杂得多。希望这将作为一个起点。
在@kazemakase 和@jagerman(后者通过 pybind11 forum)的帮助下,我已经弄明白了。 class 本身应该有一个可以从一些输入复制的构造函数,这里使用迭代器:
#include <vector>
#include <assert.h>
#include <iterator>
template <class T> class Matrix3D
{
public:
std::vector<T> data;
std::vector<size_t> shape;
std::vector<size_t> strides;
Matrix3D<T>() = default;
template<class Iterator>
Matrix3D<T>(const std::vector<size_t> &shape, Iterator first, Iterator last);
};
template <class T>
template<class Iterator>
Matrix3D<T>::Matrix3D(const std::vector<size_t> &shape_, Iterator first, Iterator last)
{
shape = shape_;
assert( shape.size() == 3 );
strides.resize(3);
strides[0] = shape[2]*shape[1];
strides[1] = shape[2];
strides[2] = 1;
int size = shape[0] * shape[1] * shape[2];
assert( last-first == size );
data.resize(size);
std::copy(first, last, data.begin());
}
直接包装如下签名的函数:
Matrix3D<double> func ( const Matrix3D<double>& );
需要以下包装代码
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/numpy.h>
namespace py = pybind11;
namespace pybind11 { namespace detail {
template <typename T> struct type_caster<Matrix3D<T>>
{
public:
PYBIND11_TYPE_CASTER(Matrix3D<T>, _("Matrix3D<T>"));
// Conversion part 1 (Python -> C++)
bool load(py::handle src, bool convert)
{
if ( !convert and !py::array_t<T>::check_(src) )
return false;
auto buf = py::array_t<T, py::array::c_style | py::array::forcecast>::ensure(src);
if ( !buf )
return false;
auto dims = buf.ndim();
if ( dims != 3 )
return false;
std::vector<size_t> shape(3);
for ( int i = 0 ; i < 3 ; ++i )
shape[i] = buf.shape()[i];
value = Matrix3D<T>(shape, buf.data(), buf.data()+buf.size());
return true;
}
//Conversion part 2 (C++ -> Python)
static py::handle cast(const Matrix3D<T>& src, py::return_value_policy policy, py::handle parent)
{
std::vector<size_t> shape (3);
std::vector<size_t> strides(3);
for ( int i = 0 ; i < 3 ; ++i ) {
shape [i] = src.shape [i];
strides[i] = src.strides[i]*sizeof(T);
}
py::array a(std::move(shape), std::move(strides), src.data.data() );
return a.release();
}
};
}} // namespace pybind11::detail
PYBIND11_PLUGIN(example) {
py::module m("example", "Module description");
m.def("func", &func, "Function description" );
return m.ptr();
}
请注意,现在也可以进行函数重载。例如,如果存在具有以下签名的重载函数:
Matrix3D<int > func ( const Matrix3D<int >& );
Matrix3D<double> func ( const Matrix3D<double>& );
需要以下包装函数定义:
m.def("func", py::overload_cast<Matrix3D<int >&>(&func), "Function description" );
m.def("func", py::overload_cast<Matrix3D<double>&>(&func), "Function description" );
我正在尝试使用 pybind11
包装我的 C++ 代码。在 C++ 中,我有一个 class Matrix3D
充当 3-D 数组(即形状 [n,m,p]
)。它具有以下基本签名:
template <class T> class Matrix3D
{
public:
std::vector<T> data;
std::vector<size_t> shape;
std::vector<size_t> strides;
Matrix3D<T>();
Matrix3D<T>(std::vector<size_t>);
Matrix3D<T>(const Matrix3D<T>&);
T& operator() (int,int,int);
};
为了最小化包装代码,我想将此 class 直接转换为 NumPy 数组(副本没有问题)。例如,我想直接包装一个具有以下签名的函数:
Matrix3D<double> func ( const Matrix3D<double>& );
使用封装代码
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/numpy.h>
namespace py = pybind11;
PYBIND11_PLUGIN(example) {
py::module m("example", "Module description");
m.def("func", &func, "Function description" );
return m.ptr();
}
目前我有另一个接受和 returns py::array_t<double>
的函数。但是我想通过用一些模板替换它来避免为每个函数编写一个包装函数。
已为 Eigen
-库(用于数组和 (2-D) 矩阵)完成此操作。但是代码太复杂了,我无法从中导出自己的代码。另外,我真的只需要包装一个,简单,class。
我对pybind11不是很熟悉,但是看了这个问题就产生了兴趣。从 documentanion 看来,您必须自己编写 type caster。这显然是一个相当高级的主题,但似乎可以通过一些努力实现。
从文档中删除,这是用于转换 C++ 类型的转换器的 shell inty
:
namespace pybind11 { namespace detail {
template <> struct type_caster<inty> {
public:
PYBIND11_TYPE_CASTER(inty, _("inty"));
// Conversion part 1 (Python->C++)
bool load(handle src, bool);
//Conversion part 2 (C++ -> Python)
static handle cast(inty src, return_value_policy, handle);
};
}} // namespace pybind11::detail
看来你所要做的就是将inty
替换为Matrix3D<double>
并实现load()
和cast()
.
让我们看看他们是如何为 Eigen 做的(eigen.h, line 236 以后):
bool load(handle src, bool) {
auto buf = array_t<Scalar>::ensure(src);
if (!buf)
return false;
auto dims = buf.ndim();
if (dims < 1 || dims > 2)
return false;
auto fits = props::conformable(buf);
if (!fits)
return false; // Non-comformable vector/matrix types
value = Eigen::Map<const Type, 0, EigenDStride>(buf.data(), fits.rows, fits.cols, fits.stride);
return true;
}
这看起来并不难。首先,他们确保输入的类型为 array_t<Scalar>
(在您的情况下可能是 array_t<double>
)。然后他们检查尺寸和一些一致性(您可以跳过后者)。最后创建特征矩阵。由于复制不是问题,此时只需创建一个新的 Martix3D<double>
实例并用 numpy 数组中的数据填充它。
对于左值和常量的不同情况,cast()
函数有不同的实现。我想只做一个在新的 numpy 数组中创建数据副本的实现就足够了,如果这样的话。请参阅函数 eigen_array_cast()
如何将数组 return 作为 handle
return 类型。
我还没有测试过这些,这个过程可能比看起来要复杂得多。希望这将作为一个起点。
在@kazemakase 和@jagerman(后者通过 pybind11 forum)的帮助下,我已经弄明白了。 class 本身应该有一个可以从一些输入复制的构造函数,这里使用迭代器:
#include <vector>
#include <assert.h>
#include <iterator>
template <class T> class Matrix3D
{
public:
std::vector<T> data;
std::vector<size_t> shape;
std::vector<size_t> strides;
Matrix3D<T>() = default;
template<class Iterator>
Matrix3D<T>(const std::vector<size_t> &shape, Iterator first, Iterator last);
};
template <class T>
template<class Iterator>
Matrix3D<T>::Matrix3D(const std::vector<size_t> &shape_, Iterator first, Iterator last)
{
shape = shape_;
assert( shape.size() == 3 );
strides.resize(3);
strides[0] = shape[2]*shape[1];
strides[1] = shape[2];
strides[2] = 1;
int size = shape[0] * shape[1] * shape[2];
assert( last-first == size );
data.resize(size);
std::copy(first, last, data.begin());
}
直接包装如下签名的函数:
Matrix3D<double> func ( const Matrix3D<double>& );
需要以下包装代码
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/numpy.h>
namespace py = pybind11;
namespace pybind11 { namespace detail {
template <typename T> struct type_caster<Matrix3D<T>>
{
public:
PYBIND11_TYPE_CASTER(Matrix3D<T>, _("Matrix3D<T>"));
// Conversion part 1 (Python -> C++)
bool load(py::handle src, bool convert)
{
if ( !convert and !py::array_t<T>::check_(src) )
return false;
auto buf = py::array_t<T, py::array::c_style | py::array::forcecast>::ensure(src);
if ( !buf )
return false;
auto dims = buf.ndim();
if ( dims != 3 )
return false;
std::vector<size_t> shape(3);
for ( int i = 0 ; i < 3 ; ++i )
shape[i] = buf.shape()[i];
value = Matrix3D<T>(shape, buf.data(), buf.data()+buf.size());
return true;
}
//Conversion part 2 (C++ -> Python)
static py::handle cast(const Matrix3D<T>& src, py::return_value_policy policy, py::handle parent)
{
std::vector<size_t> shape (3);
std::vector<size_t> strides(3);
for ( int i = 0 ; i < 3 ; ++i ) {
shape [i] = src.shape [i];
strides[i] = src.strides[i]*sizeof(T);
}
py::array a(std::move(shape), std::move(strides), src.data.data() );
return a.release();
}
};
}} // namespace pybind11::detail
PYBIND11_PLUGIN(example) {
py::module m("example", "Module description");
m.def("func", &func, "Function description" );
return m.ptr();
}
请注意,现在也可以进行函数重载。例如,如果存在具有以下签名的重载函数:
Matrix3D<int > func ( const Matrix3D<int >& );
Matrix3D<double> func ( const Matrix3D<double>& );
需要以下包装函数定义:
m.def("func", py::overload_cast<Matrix3D<int >&>(&func), "Function description" );
m.def("func", py::overload_cast<Matrix3D<double>&>(&func), "Function description" );