Boost.Pythonの使い方とNumpy/Scipy arrayの渡し方

scipyで書いたプログラムでも一部が遅くてそこをC++で実装したいというシナリオはよくある.
それには色々な手段があるというのは以前の日記でも紹介した通り. 今回はBoost.Pythonでscipy/numpyのarray(ndarray)を取り扱う方法を説明する. 基本的な使い方はboost-python ではじめる大規模機械学習とかBoost.Python の機能をざっと紹介してみるも参考になる.

簡単な使い方

さて本題へ. boost.Pythonにおいてnumpy.ndarrayに対応するクラスは, boost::python::numericである.
これは

Boost.Python には、PythonC++ の型の相互変換を行うためのコンバータという機構が備わっており、すべての値の受け渡しにはこのコンバータが介在している。

とあるように, numpy.ndarray <=> numeric という相互変換をBoostが行うということである.
1次元のndarrayの全要素を足し合わせるコードを次に示そう.

include <Python.h>
#include <numpy/arrayobject.h>
#include <boost/python.hpp>
#include <boost/python/numeric.hpp>

using namespace std;
using namespace boost;
using namespace boost::python;

double trace(boost::python::numeric::array& y, int n)
{
  double thesum = 0;
  for(int i = 0; i < n; i++)
    {
      thesum += boost::python::extract<double>(y[i]);
    }

  return thesum;
}

BOOST_PYTHON_MODULE(boost_example){
  import_array();
  numeric::array::set_module_and_type("numpy", "ndarray");
  
  def("trace",trace);
}

解説していこう.

import_array();

は, 呼ばなくてもちゃんと動くので, いらないような気もするが,こちらのページ

the extension must call import_array() in its initialization function

呼べと書いてあるので, とりあえず呼んでいる.

numeric::array::set_module_and_type("numpy", "ndarray");

は, おそらく相互変換テーブルにndarrayとnumericが対応することを登録するのだろう. これを怠ると,

Boost.Python.ArgumentError: Python argument types in
boost_example.trace(numpy.ndarray, int)
did not match C++ signature:
trace(boost::python::numeric::array {lvalue}, int)

と出る. 後は

thesum += boost::python::extract(y[i]);

で, 値を足しあわせている. extractPythonオブジェクトのベースクラスに対応する, boost::python::objectをC++の型にダウンキャストする仕組みらしい. 相互変換テーブルを使っていてもいいなぁと思ってるけど詳細は確かめていない.

問題点

まだ, あまり使用していないので, 詳細はわからないが次のような点が僕には未解決だ.

  • dtype=intのarrayが渡せない
  • 2次元以上のarrayが渡せない
  • 行列のサイズが取得できない

1つ目と2つ目の問題は相互変換テーブルにこれらの型が登録されないことにあるっぽい.

こちらの掲示板での議論によると, numericが不便だという認識は結構あるっぽい(Boost.Pythonコンパイル時に, NumPyの存在を仮定できないのが辛いっぽい?).

そこでより便利なwrapperがいくつも提案されているようだ. num_utilっていう全然更新されて無さそうな奴とか, ndarrayっていうドキュメントが無さそうな奴とか. pyublasが一番ドキュメントが充実していそうなので次はそれを使う予定.