オッサンはDesktopが好き

自作PCや機械学習、自転車のことを脈絡無く書きます

Python: Boost.numpyを使用したc++ライブラリと(多次元)配列の受け渡しをする

This is personal note.
About installation of boost.numpy, I used this page*1 as reference.

0. Environment

1. Installation

sudo su - 
cd /opt/
wget https://dl.bintray.com/boostorg/release/1.67.0/source/boost_1_67_0.tar.gz
tar zxvf boost_1_67_0.tar.gz
cd boost_1_67_0
./bootstrap.sh --with-python-version=3.6
./b2 --prefix=/opt/boost_1_67_0 install

For installation check

find / -name *boost_numpy*

f:id:changlikesdesktop:20200813093417p:plain:w600

2. Sample program

sample.cpp

#include "boost/python/numpy.hpp"
#include <stdexcept>
#include <algorithm>

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

using namespace std;

np::ndarray multiply_matrix(np::ndarray a, np::ndarray b, int size)
{
    int nd = a.get_nd();
    if (nd != 1)
    {
        throw std::runtime_error("a must be 1-dimensional");
    }
    if (a.get_dtype() != np::dtype::get_builtin<double>())
    {
        throw std::runtime_error("a must be float64 array");
    }
    double *A = reinterpret_cast<double *>(a.get_data());

    nd = b.get_nd();
    if (nd != 1)
    {
        throw std::runtime_error("b must be 1-dimensional");
    }
    if (b.get_dtype() != np::dtype::get_builtin<double>())
        throw std::runtime_error("b must be float64 array");
    double *B = reinterpret_cast<double *>(b.get_data());
    
    vector<double> ma;
    for(int i = 0; i < size; i++)
    {
        for(int j = 0; j < size; j++)
        {
            double tmp = 0.0;
            for(int k = 0; k < size; k++)
            {
                tmp += A[i*size + k]*B[k*size + j];
            }
            ma.push_back(tmp);
        }
    }

    Py_intptr_t shape[2] = {size, size};
    np::ndarray result = np::zeros(2, shape, np::dtype::get_builtin<double>());
    std::copy(ma.begin(), ma.end(), reinterpret_cast<double*>(result.get_data()));
    return result;
}

BOOST_PYTHON_MODULE(libsample) {
  Py_Initialize();
  np::initialize();
  p::def("multiply_matrix", multiply_matrix);
}

CMakeList.txt

project(sample)
cmake_minimum_required(VERSION 3.0)

set(BOOST_ROOT /opt/boost_1_67_0)

### C++11
add_compile_options(-std=c++11)

### pkgconfig (for pkg_check_modules)
find_package(PkgConfig REQUIRED)

### Python includes
pkg_check_modules(PYTHON3 python3 REQUIRED)
include_directories(${PYTHON3_INCLUDE_DIRS})

### Boost includes
include_directories(${BOOST_ROOT}/include)
link_directories(${BOOST_ROOT}/lib)

### Build
add_library(sample SHARED sample.cpp)
set_target_properties(sample PROPERTIES SUFFIX ".so")

target_link_libraries(sample boost_numpy36 boost_python36)

sample.py

import numpy as np
import libsample as s
import random

if __name__ == "__main__":
    a = np.zeros([5, 5])
    b = np.zeros([5, 5])
    for i in range(5):
        for j in range(5):
            a[i, j] = random.uniform(-10.0, 10.0)
            b[i, j] = random.uniform(-10.0, 10.0)
    print(np.matmul(a, b))

    result = s.multiply_matrix(a.reshape([5*5]), b.reshape(5*5), 5)
    print(result)

Note that input must be 1-dimentilnal, but output is OK for multi-dementional

3. Operation

mkdir build
cd build
cmake ..
make
cd ..
cp build/*.so ./
python3 sample.py

4. Afterword

Next step is debugging...