#目的
boost.pythonやboost.numpyを利用することで、c++でnumpy.ndarrayを扱えるpythonのモジュールを簡単に作成することができます。
boost.numpyは良くできたライブラリですが、イテレータが使えないなどデータへのアクセスにやや不便な点があります。そのため、データへ簡単にアクセスできるようにします。
背景
numpy.ndarrayとは
pythonのパッケージの一つnumpyが提供する多次元配列です。これなしにpythonでの数値計算があり得ないくらい広く使われています。
boost.numpyとは
numpyをc++から操作できるようにするライブラリです。以下のような感じでデータにアクセスします。
namespace bp = boost::python;
namespace np = boost::numpy;
void func(np::ndarray &data2d) {
// data2d は二次元配列としてi,j要素に0を代入
for (int i = 0; i < data2d.shape(0); ++i) {
for (int j = 0; j < data2d.shape(1); ++j) {
data2d[bp::make_tuple(i, j)] = 0.0;
}
}
}
小技の条件
この小技を利用するためには、以下の条件が満たされる必要があります。
- 配列の次元数、要素型がコンパイル時定数である
- numpy.ndarrayのデータのメモリが連続である
一つ目の条件は多くの場合で満たされるを思います。また、二つ目に関しても、元々は大きな配列から一部を切り出した変数でない限りは、満たされるはずです。
イテレータを使えるようにする
利用するのはboost.multi_array_refです。
numpy.ndarrayがpythonの多次元配列なので、c++でも多次元配列のライブラリを利用します。
boost.multi_array_refはboost.multi_arrayの自分でメモリを確保しないバージョンです。boost.multi_arrayの詳しい説明はこちらをどうぞ。(boost::multi_array - Kmonos.net) ちなみに、boost.const_multi_array_refという読み込みのみで、書き込みできないバージョンもあります。
具体例
void func(np:ndarray &data2d) {
const std::array<int, 2> shape = {data2d.shape(0), data2d.shape(1)};
boost::multi_array_ref<double, 2> wrapper(reinterpret_cast<double*>(data2d.get_data()), shape);
// i,j要素へはwrapper[i][j]でアクセスできるがイテレータを利用
for (boost::multi_array_ref_double, 2>::subarray<1>::type &&sub : wrapper) {
boost::fill(sub, 0.0);
}
}
そうすると、こんな感じにアクセスできます。
もちろん、boost.multi_array_refを利用することによるオーバーヘッドはあります。また、ポインタを直接操作する方法(C++でPythonを拡張するためのBoost.NumPyチュートリアル(実践編))もありますが、多少のオーバヘッドは気にせず安全策でいきたいと思います。
ちなみに、上記の例ではわかりやすさや、紹介の意味もあるのでboost::multi_array_ref<double, 2>
と2次元配列のラッパーを用意していますが、
const std::array<int, 1> shape = {data2d.shape(0) * data2d.shape(1)};
boost::multi_array_ref<double, 1> wrapper(reinterpret_cast<double*>(data2d.get_data()), shape);
boost::fill(wrapper, 0.0);
とすることで、一発で処理が完了します。
最後に
やっぱりここでもboostライブラリは頼りになります。
他にいい方法(boost.multi_array_ref以外のライブラリ、boost.numpyに代わる何かなど)をご存知の方は、是非ともお知らせください。