pybind11 で numpy データ(`py::array`)を扱うメモ


pybind11 v2.6 or later を想定します.

pybind11 では, numpy データ(クラス)が大体一通り用意されています.


ドキュメントにはあまり詳しい記述がなさそうですので, 詳細は .h や test code を見ることになりそうです.

## `py::buffer` or `py::array`

https://pybind11.readthedocs.io/en/stable/advanced/pycpp/numpy.html?highlight=numpy#buffer-protocol

`py::buffer` は Python buffer protocol を使って生バイナリを扱うような感じです. numpy 自体も内部データ構造では buffer protocol を利用しており, C++ 側で扱うぶんには NumPy(の C コード) は不要なのですが, Python 側からは `py::array` の場合は numpy が必要になります.

Python 側で numpy 使いたくない場合は `py::buffer` なりで頑張ることになります.

## `py::buffer_info`, `py::buffer`

https://pybind11.readthedocs.io/en/stable/advanced/pycpp/numpy.html#buffer-protocol

`def_buffer` でのみ利用できます.

`def` などでメソッドとして buffer_protocol を返すことはできません.

`Unable to convert function return value to a Python type!`

とエラーが出ます...

専用の C++ class を作ることになります... ちょいめんどい...

グローバル配列定数などであれば memoryview を使うのもできます.


## `py::array`

前述のとおり, pybind11 の配列表現 `py:array` or 明示的な型指定の `py::array<T>` は, numpy 配列として扱われます.

Python 標準の `array` モジュール(`array.array`) の `array` 型にマップされるわけではないので注意ください(ややこしいですね). 
また, pybind11 では Python native の `array` モジュールに対応する binding は提供されていません...


pybind11 内部および numpy 自体では, 実際のところはこの配列(`numpy::array` 型)は Python buffer protocol として実装されています.

pybind11 の実装としては numpy の型として見せるのにに必要な定義は pybind11 のヘッダにあるので, 別途 numpy(C) のヘッダなどはコンパイルするのには不要ですが, python 側からのアクセスでは(`C++` 側から `numpy` を import しているため), numpy が必要になります.

## print

`py::print` で, C++ 側で Python `print` 相当ができます. デバッグに役立つでしょう.  

e.g. 

```c++

py::array_t<double> nd;

py::print("ndarray = ", nd);

```

ただ, `shape()` はポインタを返すため, そのままでは `shape` 情報を print でうまく行えません.

とりあえずは helper 関数を書いて,

```c++

template<typename T>
std::string to_string(T *ptr, size_t n)
{
  std::ostringstream ss;

  ss << "(";
  for (size_t i = 0 ;i < n; i++) {
    ss << ptr[i];
    if (i != (n - 1)) {
      ss << ", ";
    }
  }
  ss << ")";

  return ss.str();
}

py::print("shape = ", to_string(nd.shape(), nd.ndim()));
```

のようにします.

## dtype

dtype はそれ自体が python object(`py::dtype`) になっています.

### 型の比較

`is()` で比較できます. 
dtype は `of()` などで生成することがきます(文字列で指定して dtype オブジェクト生成も可能).

`==` や `!=` での比較は推奨されません(warning が出る)

```c++
py::array a = ...;

auto f32ty = py::dtype::of<float>();

if (!f32ty.is(a.dtype())) {
}
```

### dtype の文字列表現

型が合わないなどで, dtype の文字列表現を取得してエラーメッセージを出したいときがあります.

`py::str(a.dtype())` として `py::str` オブジェクトを作ります.
(`a.dtype().str()` は推奨されず warning になる)

文字列表現は `py::str` で python オブジェクトのままなので, `std::string` に変換したい場合は明示的にキャストが必要です.

```c++
  void from_numpy(const py::array &a) {
    auto f32ty = py::dtype::of<float>();

    if (!f32ty.is(a.dtype())) {
      throw py::type_error("`float32` dtype expected but input array has dtype `" + std::string(py::str(a.dtype())) + "`");
    }
     
    ...
  }
```

### 値の取得

`data()` で取得できます. 多次元配列の場合は `data(0, 3)` などとできます.
形無し(`py::array`)だと `void *` でデータが返ります.

ほしい型が既知の場合は, 明示的にキャストしてもよいですが, 以下のように `py::array_t<T>` のオブジェクトを作るとよいでしょうか.
(内部ではコピーを作ってしまうのかな?)

```c++
py::array a = ...;

py::array_t<float> f(a);

const float *v = f.data(0);
```

### 値の設定

基本配列データは const なので `mutable_***` な関数で const 外し(mutable アクセス)が必要です.

```c++
static py::array_t<double> gen_arr(int n)
{
  py::array_t<double> a;
  a.resize({n, n});


  for (size_t i = 0; i < a.shape(0); i++) {
    for (size_t j = 0; j < a.shape(1); j++) {
      a.mutable_at(i, j) = double(i + j);
    }
  }

  return a;
}
```

memcpy とかで一括処理したい場合は, buffer protocol にして, `ptr` で生ポインタでアクセスが楽でしょうか.

```c++
py::array<float> arr;

py::buffer_info b = arr.request();

const float *ptr = static_cast<float *>(b.ptr);
```

データにアクセスするその他の方法が document にあります.

https://pybind11.readthedocs.io/en/stable/advanced/pycpp/numpy.html?highlight=numpy#direct-access