C++のxtensorのPythonバインディングを扱う

hatappi.hateblo.jp

前々回の記事でC++製のxtensorという多次元配列を扱うライブラリをさわった記事を書いた。
この時Pythonバインディングもあると書いたので、今回はそれを使用していく。

github.com

自分が理解出来てない部分もあるかもしれないが、README.mdだと動かなかったので所々調整しつつ動かすまではいったのでそのあたりも書いていく。

今回のゴールとしては C++製のxtensorで多次元配列を扱ってみる - hatappiのブログ でも例で書いた行列の足し算とnumpyでいうところのsumを使った総和の2つをxtensorで定義してそれをpythonから扱えるようにするところ。

準備

xtensor-python自体のインストールは conda install -c conda-forge xtensor-python で終わるので楽。
ただ実際に動作にいたるまでは色々準備が必要。
まずxtensor-pythonはpybind11をベースに書かれているので前回のpybind11、xtensorが必要なのでそれらを用意する必要がある。
この導入に関しては過去の記事を参照する。

hatappi.hateblo.jp hatappi.hateblo.jp

今回もDocker上で動かせるようにしていてDockerfileは下記のようなものになった。

FROM ubuntu:16.04
MAINTAINER Yusaku Hatanaka (hatappi)

RUN apt-get update -y && \
    apt-get install -y \
    python3-dev python3-pip wget bzip2 cmake git

RUN apt-get install apt-file -y && \
    apt-file update && \
    apt-file search add-apt-repository && \
    apt-get install software-properties-common  -y

RUN add-apt-repository ppa:ubuntu-toolchain-r/test && \
    apt-get update && \
    apt-get install g++-7 -y

RUN wget https://repo.continuum.io/archive/Anaconda3-4.4.0-Linux-x86_64.sh && \
    bash Anaconda3-4.4.0-Linux-x86_64.sh -b

RUN /root/anaconda3/bin/conda install -y -c conda-forge xtensor

RUN pip3 install pytest
RUN git clone https://github.com/pybind/pybind11.git /usr/local/src/pybind11
RUN cd /usr/local/src/pybind11 && \
    mkdir build && \
    cd build && \
    cmake .. && \
    make check -j 4

RUN /root/anaconda3/bin/conda install -y -c conda-forge xtensor-python

このImageできる環境から今回で使いそうな部分を書いておくと

のようなものを使用する。

この後の検証にはこのDokcerfileから作ったImageを使用する。
ちなみにDocker hubにもpushしてある。

https://hub.docker.com/r/hatappi/xtensor-python/

検証

まず今回使用したプログラムとしては下記を使用する。

#include <numeric>                        // Standard library import for std::accumulate
#include "pybind11/pybind11.h"            // Pybind11 import to define Python bindings
#include "xtensor/xmath.hpp"              // xtensor import for the C++ universal functions
#define FORCE_IMPORT_ARRAY
#include "xtensor-python/pyarray.hpp"     // Numpy bindings

PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr<T>);

xt::pyarray<double> add(xt::pyarray<double>& x, xt::pyarray<double>& y)
{
    return x + y;
}

xt::pyarray<double> all_sum(xt::pyarray<double>& x)
{
    return xt::sum(x);
}

xt::pyarray<double> sum(xt::pyarray<double>& x, int axis)
{
    return xt::sum(x, {axis});
}

PYBIND11_MODULE(xtensor, m)
{
    xt::import_numpy();
    m.def("add", &add, "x add y");
    m.def("all_sum", &all_sum, "sum x");
    m.def("sum", &sum, "sum x by axis");
}

今回は公式のREADME.mdのサンプルを最初見ながらやっていたのですが、サンプルのコードの中で使用されているPYBIND11_PLUGINは今年の5月末くらいのPRからdeprecatedになって今はPYBIND11_MODULEを使用するようになっていたので今回のコードではPYBIND11_MODULEに変更しました。

コードが出来たら後はビルドをするだけ

$ g++-7 -O3 -shared -std=c++14 -fPIC -I /usr/local/src/pybind11/include -I /root/anaconda3/include -I /root/anaconda3/lib/python3.6/site-packages/numpy/core/include `python3-config --cflags --ldflags` xtensor.cpp -o xtensor.so

今回は xtensor.cppというC++のファイルを用意して xtensor.so というファイルを出力させました。
後はpythonのREPLを使って下記のように検証するだけ。

>>> import xtensor
>>> xtensor.all_sum([[1, 2, 3], [4, 5, 6]])
array(21.0)
>>> xtensor.sum([[1, 2, 3], [4, 5, 6]], 0)
array([ 5.,  7.,  9.])
>>> xtensor.sum([[1, 2, 3], [4, 5, 6]], 1)
array([  6.,  15.])

最後に

まだC++力が足りないのでつけていきたい