C++でPythonを拡張するためのBoost.NumPyチュートリアル(実践編)

C++ Advent Calender 10日目の記事です。

Boost 1.63でBoost.NumPyがBoost.Pythonにマージされます。
それに伴い以下の記述では不具合があるかもしれません。修正記事を書く予定です。

## 動機
Pythonまじ便利です。
シミュレーションとその結果の解析がメインとなるアカデミア(見習い)である私には、
対話的にデータの加工、解析、可視化がシームレスに実行できるIPython Notebookは必須です。
残念ならがC++だけで同等の機能を提供してくれる環境はありません(たぶん)。
CERNが作ってる[ROOT](http://root.cern.ch/drupal/)のclingはC++を対話的に実行できるそうですが情報が少なくて使ったことありません。
だれかC++で文芸的プログラミングできるIC++ Notebook作ってください（切実）。

しかしながらシミュレーション自体は数日から数週間実行するもので、
速度が必須でありPythonなんて遅い言語で書けません。
なのでシミュレーションはC++で書く事になります（Fortranなにそれおいしいの）。

一方で[scikit-learn](http://scikit-learn.org/stable/)や[PyMC](https://github.com/pymc-devs/pymc)のようなプロジェクトがPython用のフロントエンドを用意している所を見ると、
今後暫くは他の言語で実装してPythonから使うという形が一般的になっていくような気がします。
ということで、Pythonで使える関数をC++で書くための方法のうち、
[Boost.NumPy](https://github.com/ndarray/Boost.NumPy)を使う方法についてまとめます。


### 何故Boost.NumPy?

私は以前一度Boost.Pythonを使おうとしてコンバータが理解できずに挫折しましたが、
Boost.NumPyは非常に簡単に使えました(大事)。

簡単に他の選択肢を述べておくと、
今回紹介するBoost.NumPyはシンプルにPythonの`numpy.ndarray`のC++用ラッピングを構築する方法を取ります。
C++には既にEigenのような線形代数演算を実行するライブラリが(無数に)あるので、
`numpy.ndarray`をそのままEigenの多次元ベクトルに変換する[選択肢](https://pypi.python.org/pypi/minieigen)もあります。
もっと単純にBoost.Pythonのみを使用してC++の`vector`などをPythonのlistに変換し、
その後`numpy.ndarray`に変換する選択肢もあります。
他にも[SWIG](http://www.swig.org/)や[Cython](http://cython.org/)等沢山あります。

## 目的
[前回の記事](http://qiita.com/termoshtt/items/0103803c40331c77c727)でインストールとコンパイルの仕方をまとめたので今回は実際に使う方法をまとめます。
通常、C++のコードのインターフェイス部分だけをPython側から使いたいの思いますので、
難しい事は避けてなるべく簡単に使う方法をまとめます。

Boost.Pythonに関する説明はしないので、各自で調べてください。
以下にBoost.Pythonの解説サイトを挙げます：

- [let's boost](http://www.kmonos.net/alang/boost/classes/python.html)
- [のんびりしているエンジニアの日記](http://nonbiri-tereka.hatenablog.com/search?q=boost%3A%3Apython)
- [Boost.Python の機能をざっと紹介してみる](http://d.hatena.ne.jp/moriyoshi/20091214/1260779899)

- [Boost.Python](http://www.boost.org/doc/libs/1_57_0/libs/python/doc/) 本家
- [Boost.Python（日本語訳)](http://alpha.sourceforge.jp/devel/boost.python_ja.pdf) 本家(和訳)

## Tutorial for Boost.NumPy

以下はBoost.NumPyのチュートリアルを元に私が書いたものです。
表記の複雑性の排除のため以下の様にnamespaceを略記します。

```cpp
namespace p = boost::python;
namespace np = boost::numpy;
```

### `np::ndarray`の使い方(一次元の場合)
前置きが長かったので、さっさと動くコードを載せましょう：

```cpp:mymod1.cpp
#include "boost/numpy.hpp"
#include <stdexcept>
#include <algorithm>

namespace p = boost::python;
namespace np = boost::numpy;

/* 2倍にする */
void mult_two(np::ndarray a) {
  int nd = a.get_nd();
  if (nd != 1)
    throw std::runtime_error("a must be 1-dimensional");
  size_t N = a.shape(0);
  if (a.get_dtype() != np::dtype::get_builtin<double>())
    throw std::runtime_error("a must be float64 array");
  double *p = reinterpret_cast<double *>(a.get_data());
  std::transform(p, p + N, p, [](double x) { return 2 * x; });
}

BOOST_PYTHON_MODULE(mymod1) {
  Py_Initialize();
  np::initialize();
  p::def("mult_two", mult_two);
}
```

これはそのままコンパイルできます。
一次元配列を2倍にするだけの簡単な関数ですが、重要な要素を含んでいます。

- 配列の次元は`get_nd()`を使う
- 配列の大きさは`shape(n)`を用いる
- 配列の要素の型は動的に決定され`get_dtype()`で取得する
- 生ポインタでデータにアクセスできる
- C++の`std::runtime_error`がPython側の`RuntimeError`に変換される

メモリは`np::ndarray`側で管理しているので特に意識する必要はありません。
残念ながら型が動的に決定されるので関数のオーバーロードができません。
実行時にif文で判定する必要があります。
例外を変換してくれるのは地味に便利です。

Pythonから使ってみましょう。

```python:mymod1.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import mymod1
import numpy as np

if __name__ == '__main__':
    a = np.array([1,2,3], dtype=np.float64)
    mymod1.mult_two(a)
    print(a)

    b = np.array([1,2,3], dtype=np.int64)
    mymod1.mult_two(b) # raise error
    print(b)
```

bの部分は上述の通り型が`double`でなく`long long`なのでエラーになります：

```output
[ 2.  4.  6.]
Traceback (most recent call last):
  File "/path/of/mymod1.py", line 13, in <module>
    mymod1.mult_two(b)
RuntimeError: a must be float64 array
```

なおnumpy上で整数を使用する場合、特に指定しない限り`int64`(C++だと`long long`)が使用されます。
`int`でないので注意しましょう。

オーバーロードができないなど不満は多少ありますが、
簡単にPythonで使える関数をC++で実装できました。


### `np::ndarray`の使い方(多次元の場合)
多次元の場合も基本は同じですが、メモリの並び順に気をつける必要があります。
[numpy.ndarray.stride](http://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.strides.html#numpy.ndarray.strides)の説明が分かり易いですが、以下で簡単に説明します。

2次元の配列を一つの連続したメモリ領域で管理したい事があります。

```cpp
double *a_as1 = new double[N*M];
double **a_as2 = new double*[N];
for(int i=0;i<N;++i){
  a_as2[i] = &a_as1[i*M];
}
```

こうすれば`a_as2[i][j]`で呼び出されるメモリは`a_as1[i*M+j]`と同じになります。
この`i*M+j`のような事をするための方法が`ndarray.stride`です。
今の場合j方向に1進めるのにメモリ上では8Byte進みます(doubleは8Byte)。
一方i方向に1進めるのにメモリ上では`8*M`Byte進みます(`(i+1)*M+j = i*M+j + M`)。
この`8`, `8*M`をstride(ひとまたぎ、歩幅)と呼びます。
この考え方はより高次元になっても使えます。

これに気をつければあとは簡単です。

```cpp
#include "boost/numpy.hpp"
#include <iostream>
#include <stdexcept>
#include <algorithm>

namespace p = boost::python;
namespace np = boost::numpy;

void print(np::ndarray a) {
  int nd = a.get_nd();
  if (nd != 2)
    throw std::runtime_error("a must be two-dimensional");
  if (a.get_dtype() != np::dtype::get_builtin<double>())
    throw std::runtime_error("a must be float64 array");

  auto shape = a.get_shape();
  auto strides = a.get_strides();

  std::cout << "i j val\n";
  for (int i = 0; i < shape[0]; ++i) {
    for (int j = 0; j < shape[1]; ++j) {
      std::cout << i << " " << j << " "
                << *reinterpret_cast<double *>(a.get_data() + i * strides[0] +
                                               j * strides[1]) << std::endl;
    }
  }
}

BOOST_PYTHON_MODULE(mymod2) {
  Py_Initialize();
  np::initialize();
  p::def("print", print);
}
```

strideを見て小さい順に回す方がメモリアクセスの点で高速ですが、
面倒なので省略しました。各自がんばってください。

## 最後に
PythonというかNumPyまじ便利です。
SciPyが標準的な数値計算ライブラリのインターフェイスを丁寧なドキュメント付きで用意してくれている点が大きいのでしょう。

以上で`np::ndarray`のデータにアクセスする方法をまとめたので、
C++で実装したアルゴリズムをPythonから使えるようになったと思います。
次は[PyPI](https://pypi.python.org/pypi)への投稿や、[scikits](http://scipy.org/scikits.html)へ投稿について調べたいと思います。