Cythonでnumpyを扱う  (memoryviewによる方法)



Pythonの計算を高速化するためにCythonに書き換えている。このとき、numpy配列の扱いにつまずいたのでメモした。

## 環境
ubuntu 18.04 LTS
jupyter lab 0.35.4
python 3.7.5

CUIでCythonをやるよりも、jupyterの方が楽だと思っている。setupファイルを作らなくていいので。

## Cythonでnumpyを使おうとする
まず、jupyter上では

```
%%cython
```
をつける。次に下準備。

```
import numpy as np
cimport numpy as np
cimport cython
```
numpy配列を使うために、cのtypedef宣言と同じようなことをしてから、配列を作る。ここでは、次元3のaという配列を作る。

```
ctypedef np.float64_t DTYPE_t
cdef np.ndarray[DTYPE_t, ndim=3] a
```

## 問題点
ここまでが、色々なサイトを見ると書いてあることだが、このまま実行すると以下の様なエラーが出る。

```
Error compiling Cython file:
------------------------------------------------------------


...
cimport cython

ctypedef np.float64_t DTYPE_t
ctypedef np.int_t INT_t

cdef np.ndarray[DTYPE_t, ndim=3] a
                                ^
------------------------------------------------------------

/home/qcmp/.cache/ipython/cython/_cython_magic_043176e78a21b81e31740451e1fe277a.pyx:9:33: Buffer types only allowed as function local variables

Error compiling Cython file:
------------------------------------------------------------
...

import numpy as np
^
------------------------------------------------------------

/home/qcmp/.cache/ipython/cython/_cython_magic_043176e78a21b81e31740451e1fe277a.pyx:2:0: Buffer vars not allowed in module scope
```

モジュールレベルの変数を使えず、ローカル変数だったら使えるという。
確かに、以下のようなローカル変数は、エラーが出ない。

```
cdef int x
```

## 解決法1
ローカル変数にすればよい。つまり、関数内で定義すればローカルになる。例えば、

```
def hoge():
    ctypedef np.float64_t DTYPE_t
    cdef np.ndarray[DTYPE_t, ndim=3] a
```
## 解決法2
解決法1のようにローカル変数にしたくないときもある。モジュールレベルの変数でどうしても扱いたいなと思っていたら、[このstackoverflowの回答](https://stackoverflow.com/questions/23838241/cython-says-buffer-types-only-allowed-as-function-local-variables-even-for-ndarr)に手がかりが書いてあった。
> Typically if I want to have an array as a module level variable (i.e not local to a method), I define a typed memoryview and then set it within a method

**memoryview**を使うらしい。

## メモリービューオブジェクトとは
変数をメモリとして参照するために使う。
公式ページの[型付きメモリービュー](https://cython.readthedocs.io/en/latest/src/userguide/memoryviews.html#quickstart)ページを参照すると、こう書いてある。
> 型付きメモリビューを使用すると、Pythonのオーバーヘッドを発生させることなく、基になるNumPy配列などのメモリバッファに効率的にアクセスできます。 メモリービューは、現在のNumPy配列バッファーのサポート（np.ndarray [np.float64_t、ndim = 2]）に似ていますが、より多くの機能とより簡潔な構文があります。

基本的な構文としては`[:]`を使うらしい。例えば一次元のintバッファを作るなら、

```
cdef int[:] view1D = exporting_object
```
三次元なら

```
cdef int[:,:,:] view3D = exporting_object
```
という具合である。

`exporting_object`には、
1. numpy array
2. C array
3. Cython array
のメモリービューオブジェクトを指定する。
元々numpyだけ知ればいいのだが、折角なので簡単に全部紹介する。

### 1. exporting_object が numpy arrrayの場合
```
np.arange(NUMBER , dtype) #NUMBERは任意の数
```
detypeは`dtype=np.dtype("i")`のように指定する。("i"はint)
次元を増やしたければ、`reshape`すればよい。
### 2. exporting_object が C arrayの場合
３次元なら

```
cdef int a[NUM1][NUM2][NUM3] #NUM1,2,3は任意の数。もちろんaも任意。
```
### 3 exporting_object が Cython array の場合
まず、

```
from cython.view cimport array as cvarray
```
のように`cython.view`から`array`を`cimport`しなければならない。

3次元なら

```
cvarray(shape=(NUM1,NUM2,NUM3), itemsize, format) 
```
 itemsize, formatは`itemsize=sizeof(int)`, `format="i"`のように指定する。

## 解決法2に戻る
memoryviewの紹介でnumpy以外も紹介したが、本来の問題に戻ると結局以下のように書けばよいだろう。

```
cimport numpy as np
ctypedef np.float64_t DTYPE_t
cdef DTYPE_t [:, :, :] a
```
あとは`a = np.arange(27, dtype=np.double).reshape((3, 3, 3))`などを代入すればいいだろう。
dtypeについてまたつまずいたので、別記事に書く。

## まとめ
Cythonでnumpy使うときは、1.関数内でローカル定義する　か　2.memoryviewを使う　のどちらかで解決。
## 参考
* [Cythonで使える配列バッファオブジェクト](https://qiita.com/gwappa/items/d9b0499063e88e5c89fe)
* Cython公式ページより　[Typed Memoryviews](https://cython.readthedocs.io/en/latest/src/userguide/memoryviews.html)