藻ログ

都会でOLをしています

Anaconda 環境 で Boost.Python (Numpy)

Boost.Numpy

C++Python で データのやり取りをするには C++Python の間で types のコンバーションを書いてやります.
d.hatena.ne.jp
しかし,Numpy Array に変換する機能は標準の boost.python ではサポートしてないので,例えば C++ types から 一旦 python types に変換して Python 側で ndarray に突っ込むとか,或いは protobuf にシリアライズして Python 側で protobuf から ndarray に突っ込むとかそういう感じになります.
そういったときに,C++ types → ndarray へのアクセスをダイレクトに可能にするのが Boost.Numpy です.
詳しくは公式 doc を参照されたい.
Welcome to the documentation of the Boost.Python NumPy extension! - Boost.Python NumPy extension 1.0 documentation - master

boost 1.63 から boost.numpy が同梱になった

いままで自前で boost をビルドしたり boost.python をビルドしたり *1 boost.numpy をビルドしたり 面倒だなと感じていたのですが,boost1.63 から boost.numpy が本体同梱になったので とりあえず boost のビルドが成功すればまとめて使えるようになりました.後は任意の libpython で boost をビルドすればいいのですが,色々あって OS X だと Anaconda Cloud のパッケージを使うのが楽だというところに落ち着きました.

Anaconda の Python 向けに Boost.Python を導入する

Boost :: Anaconda Cloud
conda で boost (>=1.63) をインストールします.

$ conda install -c conda-forge boost=1.64.0

こうすると/path/to/anaconda/include, /path/to/anaconda/lib 以下にヘッダとビルド済ライブラリが入ります.

とりあえず動くかテスト

適当になんか書きます.

├── CMakeLists.txt
├── include
└── src
    └── sample_py.cc

sample_py.cc
C++側で ndarray を作って Python 側で貰ってみます.*2

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-variable"
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#include <boost/python.hpp>
#include <boost/python/numpy.hpp>
#pragma GCC diagnostic pop

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

np::ndarray test() {
  py::tuple shape = py::make_tuple(3, 3);
  np::dtype dtype = np::dtype::get_builtin<float>();
  np::ndarray a = np::zeros(shape, dtype);
  return a;
}

BOOST_PYTHON_MODULE(crfpp)
{
  Py_Initialize();
  np::initialize();
  py::def("test", test);
}

cmake を書きました.今回 find_package も使わないので Makefile と殆ど変わらないですが...*3
あと,add_libraryすると勝手にターゲットの名前が libxxx にされてしまうので prefix をクリアしたのですが,これもなんとかならないのでしょうか.OS X だと .dylib ができる( Python から読めない)ので suffix で .so に変えたりもしたけどちょっと...

cmake_minimum_required(VERSION 3.3)

project(crfpp)

set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib
    CACHE PATH "Output directory for libraries.")

set(CMAKE_CXX_COMPILER clang++)
set(CMAKE_CXX_FLAGS "-Wall -Wextra -std=c++11 -O3")
set(CMAKE_SHARED_LINKER_FLAGS "-shared -fPIC")

set(ANACONDA_ROOT "/path/to/anaconda2-2.4.1")
include_directories(${ANACONDA_ROOT}/include ${ANACONDA_ROOT}/include/python2.7 "include")
link_directories(${ANACONDA_ROOT}/lib)

set(BOOST_LIBS python2.7 boost_python boost_numpy)

add_library(crfpp SHARED src/sample_py.cc)
target_link_libraries(crfpp ${BOOST_LIBS})
set_target_properties(crfpp PROPERTIES PREFIX "")
set_target_properties(crfpp PROPERTIES SUFFIX ".so")

ビルド

$ mkdir build; cd build
$ cmake ..
$ make

実行

$ cd lib
$ python -c "import crfpp; a=crfpp.test(); print(a)"
[[ 0.  0.  0.]
 [ 0.  0.  0.]
 [ 0.  0.  0.]]

ちゃんと実行できてます.よかった.

Python バインディング について

C++ -> Python 間の型コンバーションはバグを生産しやすいので,よく使う変換だけライブラリ化してそこにはあまり手をいれない運用が良いと思っている.unary_ufunc など,boost.numpy 自体の便利機能についてはまた次回書きたい

*1:私はもう二度とsconsを触ることはないでしょう

*2:なんかヘッダから Warning が出るので抑えました.

*3:find_package がどういう仕組みでパスを見に行くのかイマイチよくわからないのだが,普通にfind_package すると brew で入れた boost を見に行ってしまう