【问题标题】:Cast NumPy array to/from custom C++ Matrix-class using pybind11使用 pybind11 将 NumPy 数组转换为自定义 C++ 矩阵类/从自定义 C++ 矩阵类转换
【发布时间】:2017-07-27 11:31:11
【问题描述】:

我正在尝试使用 pybind11 包装我的 C++ 代码。在 C++ 中,我有一个类 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);

};

为了最小化包装器代码,我想将这个类直接转换为 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();
}

目前我在中间有另一个函数,它接受并返回py::array_t&lt;double&gt;。但是我想通过用一些模板替换它来避免为每个函数编写一个包装函数。

这已针对Eigen-library(用于数组和(2-D)矩阵)完成。但是代码太复杂了,我无法从中派生出我自己的代码。另外,我真的只需要包装一个简单的类。

【问题讨论】:

  • 与问题无关,但如果你的矩阵类采用任意形状,为什么它会被称为Matrix3D
  • 是的,它应该采用任意形状(原则上我可以让它任意尺寸,但是对于这个特定的项目,静态 3-D 很好)。我认为如果它具有相同的尺寸,我可以使用 Eigen 的(实验性)张量模块。

标签: python c++ arrays numpy pybind11


【解决方案1】:

在@kazemakase 和@jagerman(后者通过pybind11 forum)的帮助下,我想通了。类本身应该有一个可以从某些输入复制的构造函数,这里使用迭代器:

#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" );

【讨论】:

  • 我已经在GitHub repository 中包含了完整的代码以及一些 pybind11 示例,如示例 9。
  • 我还为该类创建了一个独立的 GitHub 存储库,以及 Python 接口:cppmat
【解决方案2】:

我对 pybind11 不熟悉,但在阅读了这个问题后变得感兴趣。从文档看来,您必须编写自己的type caster。这显然是一个相当高级的话题,但似乎可以通过一些努力来实现。

从文档中剥离,这是用于转换 C++ 类型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&lt;double&gt; 并实现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&lt;Scalar&gt;(在您的情况下可能是array_t&lt;double&gt;)。然后他们检查尺寸和一些一致性(你可以跳过后者)。最后创建特征矩阵。由于复制不是问题,此时只需创建一个新的Martix3D&lt;double&gt; 实例,并用 numpy 数组中的数据填充它。

cast() 函数针对左值和常量的不同情况有不同的实现。我想如果可以的话,只做一个在新的 numpy 数组中创建数据副本的实现就足够了。请参阅函数 eigen_array_cast() 如何将数组作为 handle 返回类型返回。

我还没有测试过这些,而且这个过程可能比看起来的要多。希望这将作为一个起点。

【讨论】:

  • 我喜欢你的回答:“我不熟悉......但是......”我的思路与你的建议一致。你的建议进一步缩小了范围,直到我可以开始详细解释。 (希望)我会尽快发布我的发现。或者也许有更多经验的人会澄清一些事情。
  • 很可能我遗漏了一些琐碎的事情,但是在PYBIND11_TYPE_CASTER(Matrix3D&lt;double&gt;, _("Matrix3D&lt;double&gt;")); 行上,我收到了编译器错误no viable conversion from 'const Matrix3D&lt;double&gt;' to 'const Matrix3D&lt;double&gt; *'。另请注意,我认为第二个参数应该以某种方式是 Python 类型。从inty 的例子来看,两者都不是很清楚。
  • 据我所知,第二个参数用于函数签名(即编译器如何调用函数)。它可能不喜欢&lt;&gt; 字符。我不知道为什么它会尝试将矩阵转换为指针。您可以手动展开macro 以缩小错误范围。
猜你喜欢
  • 2018-11-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-19
相关资源
最近更新 更多